Adrian Marino

Claudio Collado

Inicialización

Fijamos la semilla para poder reproducir los experimentos. También se fija el numero de CPU’s a utilizar.

set.seed(42)
options(mc.cores = 24)

Librerias

Se importan las librerías a utilizar a lo largo de la notebook:

# install.packages(pacman)
# install.packages("https://cran.r-project.org/src/contrib/rstan_2.21.2.tar.gz",repos = NULL,type="source")
# sudo apt-get install libglpk-dev
library(pacman)
p_load(tidyverse, tidymodels, rsample, rstan, shinystan, rstanarm, devtools)
p_load_gh('adrianmarino/commons')

import('../src/dataset.R')
[1] "-> '../src/dataset.R' script loadded successfuly!"
import('../src/plot.R')
[1] "-> '../src/plot.R' script loadded successfuly!"
import('../src/model.R')
[1] "-> './bayesian_regression_predictor.R' script loadded successfuly!"
[1] "-> '../src/model.R' script loadded successfuly!"

Dataset y Analisis Exploratorio

Palmer Penguins

Palmer Penguins

1. Lectura del dataset

dataset <- load_dataset() %>% mutate_if(is.character, as.factor)
Rows: 344 Columns: 8
── Column specification ──────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (3): species, island, sex
dbl (5): bill_length_mm, bill_depth_mm, flipper_length_mm, body_mass_g, year

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
dataset %>% glimpse()
Rows: 344
Columns: 8
$ species           <fct> Adelie, Adelie, Adelie, Adelie, Adelie, Adelie, Adelie, Adelie, Adelie, Ad…
$ island            <fct> Torgersen, Torgersen, Torgersen, Torgersen, Torgersen, Torgersen, Torgerse…
$ bill_length_mm    <dbl> 39.1, 39.5, 40.3, NA, 36.7, 39.3, 38.9, 39.2, 34.1, 42.0, 37.8, 37.8, 41.1…
$ bill_depth_mm     <dbl> 18.7, 17.4, 18.0, NA, 19.3, 20.6, 17.8, 19.6, 18.1, 20.2, 17.1, 17.3, 17.6…
$ flipper_length_mm <dbl> 181, 186, 195, NA, 193, 190, 181, 195, 193, 190, 186, 180, 182, 191, 198, …
$ body_mass_g       <dbl> 3750, 3800, 3250, NA, 3450, 3650, 3625, 4675, 3475, 4250, 3300, 3700, 3200…
$ sex               <fct> male, female, female, NA, female, male, female, male, NA, NA, NA, NA, fema…
$ year              <dbl> 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007, 20…

2. Variables

Se enumeran y describen breve-mente cada variable que forma parte del dataset:

Variables Numéricas:

  • bill_length_mm: Longitud del pico del individuo medida en milímetros (también conocida como longitud del culmen).
  • bill_depth_mm: Profundidad del pico del individuo medida en milímetros (también conocida como profundidad del culmen).
  • flipper_length_mm: Longitud de la aleta del individuo medida en milímetros.
  • body_mass_g: Masa corporal del individuo medida en gramos.
  • year: Año en el que se registra el individuo.

Variables Categóricas:

  • species: Especie del individuo (Adelie, Gentoo ;) o Chinstrap).
  • sex: Sexo del individuo.
  • island: Isla donde se encontré el individuo (Biscoe, Dream o Torgersen).

A continuación veamos los posibles valores de las variables categóricas:

show_values(dataset %>% select_if(negate(is.numeric)))
_____________ 
species     n 
============= 
Adelie    152 
Chinstrap  68 
Gentoo    124 
¯¯¯¯¯¯¯¯¯¯¯¯¯ 
_____________ 
island      n 
============= 
Biscoe    168 
Dream     124 
Torgersen  52 
¯¯¯¯¯¯¯¯¯¯¯¯¯ 
__________ 
sex      n 
========== 
female 165 
male   168 
NA      11 
¯¯¯¯¯¯¯¯¯¯ 

3. Resumen de faltantes

missings_summary(dataset)

4. Varibles numericas

hist_plots(dataset)

Observaciones

  • Se aprecia que cada año se registro prácticamente el mismo numero de individuos.
  • La distribución de la masa corporal de los individuos tiene una asimétrica positiva. Tenemos muchos individuos con valores bajos de masa corporal, con una media de 192 gramos. Luego tenemos menos individuos con valores mas alto.
  • La longitud de la aleta parece ser una distribución bi-modal. Tenemos dos modas una 192 mm y otra en 215 mm.
  • La longitud del pico también parece tener una ligera simetría positiva. Es decir que lo individuos con menor peso tiene pico mas pequeños.
  • Por otro lado la profundidad de pico parece tener una ligera simetría positiva.
box_plots(dataset)

Observaciones

  • COMPLETAR

Outliers

No se registran valores mas extremos que el mínimo y máximo valor en cada variables. Es decir que no encontramos outliers.

outliers(dataset, column='bill_length_mm')
$inf
[1] 32.1

$sup
[1] 59.6

outliers(dataset, column='bill_length_mm')
$inf
[1] 32.1

$sup
[1] 59.6

outliers(dataset, column='bill_depth_mm')
$inf
[1] 13.1

$sup
[1] 21.5

outliers(dataset, column='flipper_length_mm')
$inf
[1] 172

$sup
[1] 231

outliers(dataset, column='body_mass_g')
$inf
[1] 2700

$sup
[1] 6300

outliers(dataset, column='year')
$inf
[1] 2007

$sup
[1] 2009

bar_plots(dataset)

Observaciones

  • La variable sexo se encuentra balanceada. Por otro lado, contiene algunos valores faltantes.
  • La variable island esta completamente desbalanceada. Esto seguramente se debe a una diferencia en numero en las poblaciones en cada isla o a un sesgo al momento de registrar los individuos. Es decir que registramos con individuos en una isla que en otra.
  • Lo mismo sucede con las especies de individuos. Vemos un gran desbalance entre la especie Chinstrap vs. otra especies. Por otro aldo Adelie y Gentoo tiene un conteo mas cercano

5. Excluir observaciones con missings

dataset <- dataset %>% drop_na()
missings_summary(dataset)
Warning: attributes are not identical across measure variables;
they will be dropped

6. Correlaciones

corr_plot(dataset %>% dplyr::select(-year))

segmented_pairs_plot(dataset, segment_column='species')
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Experimentos

Experimento 1

  • Solo variables numéricas.
  • Regresión múltiple frecuentista.
  • Regresión múltiple bayesiana con priors normales y exponencial.

1. Split train vs. test

train_test <- train_test_split(dataset, train_size = 0.7, shuffle = TRUE)
[1] "Train set size: 233"
[1] "Test set size: 100"
train_set <- train_test[[1]]
test_set  <- train_test[[2]]

2. Modelo lineal

lineal_model_1 <- lm(
  body_mass_g ~ bill_length_mm + bill_depth_mm + flipper_length_mm,
  data = train_set
)

3. Modelo bayesiano

bayesion_model_1 <- stan(
  model_code =  "
    data {
      int<lower=1>               obs_count;
      vector<lower=1>[obs_count] x1;
      vector<lower=1>[obs_count] x2;
      vector<lower=1>[obs_count] x3;
      vector[obs_count]          y;
    }
    parameters {
      real          beta0;
      real          beta1;
      real          beta2;
      real          beta3;
      real<lower=0> sigma;
    }
    model {
      beta0 ~ normal(0, 8000);
      beta1 ~ normal(0, 100);
      beta2 ~ normal(0, 100);
      beta3 ~ normal(0, 100);
      sigma ~ exponential(0.1);
    
      y ~ normal(beta0 + beta1 * x1 + beta2 * x2 + beta3 * x3, sigma);
    }
  ",
  data = list(
      obs_count = nrow(train_set),
      y  = colvalues(train_set, 'body_mass_g'),
      x1 = colvalues(train_set, 'bill_length_mm'),
      x2 = colvalues(train_set, 'bill_depth_mm'),
      x3 = colvalues(train_set, 'flipper_length_mm')
  ),
  chains = 3,
  iter   = 300,
  warmup = 180,
  thin   = 1
)
starting worker pid=15783 on localhost:11189 at 17:22:19.265
starting worker pid=15820 on localhost:11189 at 17:22:19.372
starting worker pid=15857 on localhost:11189 at 17:22:19.480

SAMPLING FOR MODEL '075149bad897790d99f42d10fc339899' NOW (CHAIN 1).
Chain 1: 
Chain 1: Gradient evaluation took 3.7e-05 seconds
Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 0.37 seconds.
Chain 1: Adjust your expectations accordingly!
Chain 1: 
Chain 1: 
Chain 1: Iteration:   1 / 300 [  0%]  (Warmup)
Chain 1: Iteration:  30 / 300 [ 10%]  (Warmup)
Chain 1: Iteration:  60 / 300 [ 20%]  (Warmup)
Chain 1: Iteration:  90 / 300 [ 30%]  (Warmup)
Chain 1: Iteration: 120 / 300 [ 40%]  (Warmup)
Chain 1: Iteration: 150 / 300 [ 50%]  (Warmup)
Chain 1: Iteration: 180 / 300 [ 60%]  (Warmup)
Chain 1: Iteration: 181 / 300 [ 60%]  (Sampling)
Chain 1: Iteration: 210 / 300 [ 70%]  (Sampling)
Chain 1: Iteration: 240 / 300 [ 80%]  (Sampling)
Chain 1: Iteration: 270 / 300 [ 90%]  (Sampling)
Chain 1: Iteration: 300 / 300 [100%]  (Sampling)
Chain 1: 
Chain 1:  Elapsed Time: 0.847551 seconds (Warm-up)
Chain 1:                0.301735 seconds (Sampling)
Chain 1:                1.14929 seconds (Total)
Chain 1: 

SAMPLING FOR MODEL '075149bad897790d99f42d10fc339899' NOW (CHAIN 2).
Chain 2: 
Chain 2: Gradient evaluation took 5.5e-05 seconds
Chain 2: 1000 transitions using 10 leapfrog steps per transition would take 0.55 seconds.
Chain 2: Adjust your expectations accordingly!
Chain 2: 
Chain 2: 
Chain 2: Iteration:   1 / 300 [  0%]  (Warmup)
Chain 2: Iteration:  30 / 300 [ 10%]  (Warmup)
Chain 2: Iteration:  60 / 300 [ 20%]  (Warmup)
Chain 2: Iteration:  90 / 300 [ 30%]  (Warmup)
Chain 2: Iteration: 120 / 300 [ 40%]  (Warmup)
Chain 2: Iteration: 150 / 300 [ 50%]  (Warmup)
Chain 2: Iteration: 180 / 300 [ 60%]  (Warmup)
Chain 2: Iteration: 181 / 300 [ 60%]  (Sampling)

SAMPLING FOR MODEL '075149bad897790d99f42d10fc339899' NOW (CHAIN 3).
Chain 3: 
Chain 3: Gradient evaluation took 2.8e-05 seconds
Chain 3: 1000 transitions using 10 leapfrog steps per transition would take 0.28 seconds.
Chain 3: Adjust your expectations accordingly!
Chain 3: 
Chain 3: 
Chain 3: Iteration:   1 / 300 [  0%]  (Warmup)
Chain 3: Iteration:  30 / 300 [ 10%]  (Warmup)
Chain 2: Iteration: 210 / 300 [ 70%]  (Sampling)
Chain 2: Iteration: 240 / 300 [ 80%]  (Sampling)
Chain 3: Iteration:  60 / 300 [ 20%]  (Warmup)
Chain 2: Iteration: 270 / 300 [ 90%]  (Sampling)
Chain 2: Iteration: 300 / 300 [100%]  (Sampling)
Chain 2: 
Chain 2:  Elapsed Time: 0.996172 seconds (Warm-up)
Chain 2:                0.588716 seconds (Sampling)
Chain 2:                1.58489 seconds (Total)
Chain 2: 
Chain 3: Iteration:  90 / 300 [ 30%]  (Warmup)
Chain 3: Iteration: 120 / 300 [ 40%]  (Warmup)
Chain 3: Iteration: 150 / 300 [ 50%]  (Warmup)
Chain 3: Iteration: 180 / 300 [ 60%]  (Warmup)
Chain 3: Iteration: 181 / 300 [ 60%]  (Sampling)
Chain 3: Iteration: 210 / 300 [ 70%]  (Sampling)
Chain 3: Iteration: 240 / 300 [ 80%]  (Sampling)
Chain 3: Iteration: 270 / 300 [ 90%]  (Sampling)
Chain 3: Iteration: 300 / 300 [100%]  (Sampling)
Chain 3: 
Chain 3:  Elapsed Time: 1.32862 seconds (Warm-up)
Chain 3:                0.594813 seconds (Sampling)
Chain 3:                1.92344 seconds (Total)
Chain 3: 
params_1 <- c('beta0', 'beta1', 'beta2', 'beta3', 'sigma')
traceplot(bayesion_model_1, pars = params_1, inc_warmup = TRUE)

4. Coeficientes

lm_vs_br_coeficients(lineal_model_1, bayesion_model_1, params_1)

4. Validación

vars_1 <- c('bill_length_mm', 'bill_depth_mm', 'flipper_length_mm') 

lm_vs_br_models_validation(lineal_model_1, bayesion_model_1, params_1, vars_1, test_set)
bayesion_predictor_1 <- BayesianRegressionPredictor.from(bayesion_model_1, params_1, vars_1)

plot_compare_fit(
  lineal_model_1, 
  bayesion_predictor_1, 
  train_set,
  label_1='Regresion Lineal', 
  label_2='Regresion Bayesiana'
)

Experimento 2

  • Idem a experimento 1, incorporando una variable categórica.
  • Regresión múltiple frecuentista.
  • Regresión bayesiana con priors normales y exponencial.

1. Modelo lineal

lineal_model_2 <- lm(
    body_mass_g 
      ~ bill_length_mm
      + bill_depth_mm
      + flipper_length_mm
      + sex,

  data = train_set
)

2. Modelo bayesiano

Antes que nada transformamos la columna categórica a one-hot encoding:

cat_cols <- c('sex')

train_set_cat <- train_set %>% dummify(cat_cols)
test_set_cat  <- test_set  %>% dummify(cat_cols)

train_set_cat

Construimos una matriz con todas las variables(X) mas el intercept:

to_model_input <- function(df) {
  model.matrix(
    body_mass_g 
      ~ bill_length_mm
      + bill_depth_mm
      + flipper_length_mm
      + sex_female
      + sex_male,

    data = df
  )
}

train_X <- train_set_cat %>% to_model_input()
test_X  <- test_set_cat %>% to_model_input()

Definimos y corremos el modelo bayesiano:

bayesion_model_2 <- stan(
  model_code = "
    data {
      int<lower=1>                 obs_count;
      int<lower=1>                 coef_count;
      matrix[obs_count,coef_count] X;
      vector[obs_count]            y;
    }
    parameters {
      vector[coef_count]  beta;
      real<lower=0>       sigma;
    }
    model {
      beta[1] ~ normal(0, 2000);
      
      beta[2] ~ normal(0, 30);
      beta[3] ~ normal(0, 100);
      beta[4] ~ normal(0, 100);
  
      beta[5] ~ normal(0, 100);
      beta[6] ~ normal(0, 100);
  
      sigma ~ exponential(0.1);
  
      y ~ normal(X * beta, sigma);
    }
  ",
  data = list(
      obs_count  = dim(train_X)[1],
      coef_count = dim(train_X)[2],
      y          = colvalues(train_set_cat, 'body_mass_g'),
      X          = train_X
  ),
  chains = 3,
  iter   = 300,
  warmup = 180,
  thin   = 1
)
starting worker pid=16059 on localhost:11189 at 17:22:52.890
starting worker pid=16096 on localhost:11189 at 17:22:52.997
starting worker pid=16133 on localhost:11189 at 17:22:53.107

SAMPLING FOR MODEL '23ab4c58649acf62fe6953fd9b605e50' NOW (CHAIN 1).
Chain 1: 
Chain 1: Gradient evaluation took 1.9e-05 seconds
Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 0.19 seconds.
Chain 1: Adjust your expectations accordingly!
Chain 1: 
Chain 1: 
Chain 1: Iteration:   1 / 300 [  0%]  (Warmup)
Chain 1: Iteration:  30 / 300 [ 10%]  (Warmup)
Chain 1: Iteration:  60 / 300 [ 20%]  (Warmup)
Chain 1: Iteration:  90 / 300 [ 30%]  (Warmup)
Chain 1: Iteration: 120 / 300 [ 40%]  (Warmup)
Chain 1: Iteration: 150 / 300 [ 50%]  (Warmup)
Chain 1: Iteration: 180 / 300 [ 60%]  (Warmup)
Chain 1: Iteration: 181 / 300 [ 60%]  (Sampling)
Chain 1: Iteration: 210 / 300 [ 70%]  (Sampling)
Chain 1: Iteration: 240 / 300 [ 80%]  (Sampling)
Chain 1: Iteration: 270 / 300 [ 90%]  (Sampling)
Chain 1: Iteration: 300 / 300 [100%]  (Sampling)
Chain 1: 
Chain 1:  Elapsed Time: 0.396033 seconds (Warm-up)
Chain 1:                0.142786 seconds (Sampling)
Chain 1:                0.538819 seconds (Total)
Chain 1: 

SAMPLING FOR MODEL '23ab4c58649acf62fe6953fd9b605e50' NOW (CHAIN 2).
Chain 2: 
Chain 2: Gradient evaluation took 1.8e-05 seconds
Chain 2: 1000 transitions using 10 leapfrog steps per transition would take 0.18 seconds.
Chain 2: Adjust your expectations accordingly!
Chain 2: 
Chain 2: 
Chain 2: Iteration:   1 / 300 [  0%]  (Warmup)
Chain 2: Iteration:  30 / 300 [ 10%]  (Warmup)
Chain 2: Iteration:  60 / 300 [ 20%]  (Warmup)
Chain 2: Iteration:  90 / 300 [ 30%]  (Warmup)
Chain 2: Iteration: 120 / 300 [ 40%]  (Warmup)
Chain 2: Iteration: 150 / 300 [ 50%]  (Warmup)
Chain 2: Iteration: 180 / 300 [ 60%]  (Warmup)
Chain 2: Iteration: 181 / 300 [ 60%]  (Sampling)
Chain 2: Iteration: 210 / 300 [ 70%]  (Sampling)
Chain 2: Iteration: 240 / 300 [ 80%]  (Sampling)
Chain 2: Iteration: 270 / 300 [ 90%]  (Sampling)

SAMPLING FOR MODEL '23ab4c58649acf62fe6953fd9b605e50' NOW (CHAIN 3).
Chain 3: 
Chain 3: Gradient evaluation took 1.7e-05 seconds
Chain 3: 1000 transitions using 10 leapfrog steps per transition would take 0.17 seconds.
Chain 3: Adjust your expectations accordingly!
Chain 3: 
Chain 3: 
Chain 3: Iteration:   1 / 300 [  0%]  (Warmup)
Chain 2: Iteration: 300 / 300 [100%]  (Sampling)
Chain 2: 
Chain 2:  Elapsed Time: 0.459342 seconds (Warm-up)
Chain 2:                0.144679 seconds (Sampling)
Chain 2:                0.604021 seconds (Total)
Chain 2: 
Chain 3: Iteration:  30 / 300 [ 10%]  (Warmup)
Chain 3: Iteration:  60 / 300 [ 20%]  (Warmup)
Chain 3: Iteration:  90 / 300 [ 30%]  (Warmup)
Chain 3: Iteration: 120 / 300 [ 40%]  (Warmup)
Chain 3: Iteration: 150 / 300 [ 50%]  (Warmup)
Chain 3: Iteration: 180 / 300 [ 60%]  (Warmup)
Chain 3: Iteration: 181 / 300 [ 60%]  (Sampling)
Chain 3: Iteration: 210 / 300 [ 70%]  (Sampling)
Chain 3: Iteration: 240 / 300 [ 80%]  (Sampling)
Chain 3: Iteration: 270 / 300 [ 90%]  (Sampling)
Chain 3: Iteration: 300 / 300 [100%]  (Sampling)
Chain 3: 
Chain 3:  Elapsed Time: 0.435183 seconds (Warm-up)
Chain 3:                0.123199 seconds (Sampling)
Chain 3:                0.558382 seconds (Total)
Chain 3: 
params_2 <- c('beta[1]', 'beta[2]', 'beta[3]', 'beta[4]', 'beta[5]', 'beta[6]', 'sigma')
traceplot(bayesion_model_2, inc_warmup = TRUE, pars = params_2)

3. Coeficientes

lineal_model_2$coefficients
      (Intercept)    bill_length_mm     bill_depth_mm flipper_length_mm           sexmale 
    -1922.6152066        -0.5330962       -92.9216684        37.2016333       534.1583252 
br_coeficients(bayesion_model_2, params_2)

4. Validación

vars_2 <- c('bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'sex_female','sex_male')

lm_vs_br_models_validation(
  lineal_model_2, 
  bayesion_model_2, 
  params_2,
  vars_2,
  test_set, 
  test_set_cat
)
bayesion_predictor_2 <- BayesianRegressionPredictor.from(bayesion_model_2, params_2, vars_2)

plot_compare_fit(
  lineal_model_2, 
  bayesion_predictor_2, 
  test_set, 
  test_set_cat,
  label_1='Regresion Lineal', 
  label_2='Regresion Bayesiana'
)

Experimento 3

  • Idem a experimento 1 incorporando outliers en alguna variable numérica.
  • Regresión múltiple frecuentista.
  • Regresión bayesiana con priors normales y exponencial.

1. Outliers

A continuación vamos a agregar outliers a la variable flipper_length_mm, la cual define la longitud de la aleta del individuo medida en milímetros.

Para visualizar los nuevos outliers a continuación graficamos flipper_length_mm vs. body_mass_g antes y después de agregar outliers:

plot_data(train_set)

train_set_with_outliers <- train_set %>%
  mutate(flipper_length_mm = ifelse(
    body_mass_g > 4900 & body_mass_g < 5000, 
    flipper_length_mm + (flipper_length_mm * runif(1, 0.1, 0.2)), 
    flipper_length_mm
  ))

plot_data(train_set_with_outliers)

2. Modelo lineal

lineal_model_3 <- lm(
  body_mass_g ~ bill_length_mm + bill_depth_mm + flipper_length_mm,
  data = train_set_with_outliers
)

Comparemos el ajuste del modelo lineal ajustando en un dataset de train con y sin outliers:

plot_compare_fit(
  lineal_model_1, 
  lineal_model_3, 
  train_set_with_outliers,
  label_1='Regresión Lineal SIN outliers', 
  label_2='Regresión Lineal CON outliters'
)

Se aprecia que el modelo entrenado en el train set outliers esta apalancado por las observaciones atipicas.

2. Modelo lineal Robusto

Entrenamos una regresión lineal múltiple robusta para intentar de disminuir el efecto de los nuevos outliers.

p_load(MASS)
robust_lineal_model_3 <- rlm(
  body_mass_g ~ bill_length_mm + bill_depth_mm + flipper_length_mm,
  data = train_set_with_outliers
)
plot_compare_fit(
  lineal_model_1, 
  robust_lineal_model_3, 
  train_set_with_outliers,
  label_1='Regresión Lineal SIN outliers', 
  label_2='Regresión Lineal Robusta CON outliters'
)

Gráficamente no se llega a distinguir pero el modelo robusto termina ajustando mejor que el modelo lineal clásico. Esto se puede observar cuando comparamos el RMSE/MAE sobre train.

lm_vs_lm_models_validation(lineal_model_3, robust_lineal_model_3, train_set_with_outliers)
Warning in actual - predicted :
  longer object length is not a multiple of shorter object length
Warning in actual - predicted :
  longer object length is not a multiple of shorter object length
Warning in actual - predicted :
  longer object length is not a multiple of shorter object length
Warning in actual - predicted :
  longer object length is not a multiple of shorter object length

Mas alla de esto el modelo clasico sigue dando mejores resultado en test:

lm_vs_lm_models_validation(lineal_model_3, robust_lineal_model_3, test_set)

3. Modelo bayesiano con Likelihood normal

bayesion_model_3 <- stan(
  model_code =  "
    data {
      int<lower=1>               obs_count;
      vector<lower=1>[obs_count] x1;
      vector<lower=1>[obs_count] x2;
      vector<lower=1>[obs_count] x3;
      vector[obs_count]          y;
    }
    parameters {
      real          beta0;
      real          beta1;
      real          beta2;
      real          beta3;
      real<lower=0> sigma;
    }
    model {
      beta0 ~ normal(0, 8000);
      beta1 ~ normal(0, 100);
      beta2 ~ normal(0, 100);
      beta3 ~ normal(0, 100);
      sigma ~ exponential(0.1);
    
      y ~ normal(beta0 + beta1 * x1 + beta2 * x2 + beta3 * x3, sigma);
    }
  ",
  data = list(
      obs_count = nrow(train_set_with_outliers),
      y  = colvalues(train_set_with_outliers, 'body_mass_g'),
      x1 = colvalues(train_set_with_outliers, 'bill_length_mm'),
      x2 = colvalues(train_set_with_outliers, 'bill_depth_mm'),
      x3 = colvalues(train_set_with_outliers, 'flipper_length_mm')
  ),
  chains = 3,
  iter   = 300,
  warmup = 180,
  thin   = 1
)
starting worker pid=16230 on localhost:11189 at 17:23:05.825
starting worker pid=16267 on localhost:11189 at 17:23:05.936
starting worker pid=16304 on localhost:11189 at 17:23:06.043

SAMPLING FOR MODEL '075149bad897790d99f42d10fc339899' NOW (CHAIN 1).
Chain 1: 
Chain 1: Gradient evaluation took 3.4e-05 seconds
Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 0.34 seconds.
Chain 1: Adjust your expectations accordingly!
Chain 1: 
Chain 1: 
Chain 1: Iteration:   1 / 300 [  0%]  (Warmup)
Chain 1: Iteration:  30 / 300 [ 10%]  (Warmup)
Chain 1: Iteration:  60 / 300 [ 20%]  (Warmup)
Chain 1: Iteration:  90 / 300 [ 30%]  (Warmup)
Chain 1: Iteration: 120 / 300 [ 40%]  (Warmup)
Chain 1: Iteration: 150 / 300 [ 50%]  (Warmup)

SAMPLING FOR MODEL '075149bad897790d99f42d10fc339899' NOW (CHAIN 2).
Chain 2: 
Chain 2: Gradient evaluation took 3.4e-05 seconds
Chain 2: 1000 transitions using 10 leapfrog steps per transition would take 0.34 seconds.
Chain 2: Adjust your expectations accordingly!
Chain 2: 
Chain 2: 
Chain 2: Iteration:   1 / 300 [  0%]  (Warmup)
Chain 1: Iteration: 180 / 300 [ 60%]  (Warmup)
Chain 1: Iteration: 181 / 300 [ 60%]  (Sampling)
Chain 2: Iteration:  30 / 300 [ 10%]  (Warmup)
Chain 1: Iteration: 210 / 300 [ 70%]  (Sampling)
Chain 1: Iteration: 240 / 300 [ 80%]  (Sampling)
Chain 2: Iteration:  60 / 300 [ 20%]  (Warmup)

SAMPLING FOR MODEL '075149bad897790d99f42d10fc339899' NOW (CHAIN 3).
Chain 3: 
Chain 3: Gradient evaluation took 4.1e-05 seconds
Chain 3: 1000 transitions using 10 leapfrog steps per transition would take 0.41 seconds.
Chain 3: Adjust your expectations accordingly!
Chain 3: 
Chain 3: 
Chain 3: Iteration:   1 / 300 [  0%]  (Warmup)
Chain 3: Iteration:  30 / 300 [ 10%]  (Warmup)
Chain 1: Iteration: 270 / 300 [ 90%]  (Sampling)
Chain 1: Iteration: 300 / 300 [100%]  (Sampling)
Chain 1: 
Chain 1:  Elapsed Time: 0.803172 seconds (Warm-up)
Chain 1:                0.458477 seconds (Sampling)
Chain 1:                1.26165 seconds (Total)
Chain 1: 
Chain 3: Iteration:  60 / 300 [ 20%]  (Warmup)
Chain 2: Iteration:  90 / 300 [ 30%]  (Warmup)
Chain 2: Iteration: 120 / 300 [ 40%]  (Warmup)
Chain 3: Iteration:  90 / 300 [ 30%]  (Warmup)
Chain 2: Iteration: 150 / 300 [ 50%]  (Warmup)
Chain 2: Iteration: 180 / 300 [ 60%]  (Warmup)
Chain 2: Iteration: 181 / 300 [ 60%]  (Sampling)
Chain 3: Iteration: 120 / 300 [ 40%]  (Warmup)
Chain 2: Iteration: 210 / 300 [ 70%]  (Sampling)
Chain 2: Iteration: 240 / 300 [ 80%]  (Sampling)
Chain 3: Iteration: 150 / 300 [ 50%]  (Warmup)
Chain 2: Iteration: 270 / 300 [ 90%]  (Sampling)
Chain 2: Iteration: 300 / 300 [100%]  (Sampling)
Chain 2: 
Chain 2:  Elapsed Time: 1.15532 seconds (Warm-up)
Chain 2:                0.307839 seconds (Sampling)
Chain 2:                1.46316 seconds (Total)
Chain 2: 
Chain 3: Iteration: 180 / 300 [ 60%]  (Warmup)
Chain 3: Iteration: 181 / 300 [ 60%]  (Sampling)
Chain 3: Iteration: 210 / 300 [ 70%]  (Sampling)
Chain 3: Iteration: 240 / 300 [ 80%]  (Sampling)
Chain 3: Iteration: 270 / 300 [ 90%]  (Sampling)
Chain 3: Iteration: 300 / 300 [100%]  (Sampling)
Chain 3: 
Chain 3:  Elapsed Time: 1.15584 seconds (Warm-up)
Chain 3:                0.50189 seconds (Sampling)
Chain 3:                1.65773 seconds (Total)
Chain 3: 
params_3 <- c('beta0', 'beta1', 'beta2', 'beta3', 'sigma')
traceplot(bayesion_model_3, pars = params_3, inc_warmup = TRUE)

4. Coeficientes

lm_vs_br_coeficients(robust_lineal_model_3, bayesion_model_3, params_3)

5. Validación

vars_3 <- c('bill_length_mm', 'bill_depth_mm', 'flipper_length_mm') 

lm_vs_br_models_validation(robust_lineal_model_3, bayesion_model_3, params_3, vars_3, test_set)
bayesion_predictor_3 <- BayesianRegressionPredictor.from(bayesion_model_3, params_3, vars_3)

plot_compare_fit(
  robust_lineal_model_3, 
  bayesion_predictor_3, 
  train_set,
  label_1='Regresion Lineal Robusta CON outliers', 
  label_2='Regresion Bayesiana CON outliers'
)

6. Modelo bayesiano con Likelihood t-student

Experimento 4

  • Idem experimento 1 pero reduciendo la cantidad de observaciones a pocos valores (ej:30).
  • Regresion multiple frecuentista.
  • Regresion bayesiana con priors normales y exponencial.

1. Split train - test

En este aso entrenamos solo con el 10% de lo datos.

train_test <- train_test_split(dataset, train_size = 0.05, shuffle = TRUE)
[1] "Train set size: 16"
[1] "Test set size: 317"
train_set_4 <- train_test[[1]]
test_set_4  <- train_test[[2]]
plot_data(train_set_4)

2. Modelo lineal

lineal_model_4 <- lm(
  body_mass_g ~ bill_length_mm + bill_depth_mm + flipper_length_mm,
  data = train_set_4
)

3. Modelo bayesiano

bayesion_model_4 <- stan(
  model_code =  "
    data {
      int<lower=1>               obs_count;
      vector<lower=1>[obs_count] x1;
      vector<lower=1>[obs_count] x2;
      vector<lower=1>[obs_count] x3;
      vector[obs_count]          y;
    }
    parameters {
      real          beta0;
      real          beta1;
      real          beta2;
      real          beta3;
      real<lower=0> sigma;
    }
    model {
      beta0 ~ normal(0, 8000);
      beta1 ~ normal(0, 100);
      beta2 ~ normal(0, 100);
      beta3 ~ normal(0, 100);
      sigma ~ exponential(0.1);
    
      y ~ normal(beta0 + beta1 * x1 + beta2 * x2 + beta3 * x3, sigma);
    }
  ",
  data = list(
      obs_count = nrow(train_set_4),
      y  = colvalues(train_set_4, 'body_mass_g'),
      x1 = colvalues(train_set_4, 'bill_length_mm'),
      x2 = colvalues(train_set_4, 'bill_depth_mm'),
      x3 = colvalues(train_set_4, 'flipper_length_mm')
  ),
  chains = 3,
  iter   = 300,
  warmup = 180,
  thin   = 1
)
starting worker pid=16401 on localhost:11189 at 17:23:17.446
starting worker pid=16438 on localhost:11189 at 17:23:17.556
starting worker pid=16475 on localhost:11189 at 17:23:17.664

SAMPLING FOR MODEL '075149bad897790d99f42d10fc339899' NOW (CHAIN 1).
Chain 1: 
Chain 1: Gradient evaluation took 1.2e-05 seconds
Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 0.12 seconds.
Chain 1: Adjust your expectations accordingly!
Chain 1: 
Chain 1: 
Chain 1: Iteration:   1 / 300 [  0%]  (Warmup)
Chain 1: Iteration:  30 / 300 [ 10%]  (Warmup)
Chain 1: Iteration:  60 / 300 [ 20%]  (Warmup)
Chain 1: Iteration:  90 / 300 [ 30%]  (Warmup)
Chain 1: Iteration: 120 / 300 [ 40%]  (Warmup)
Chain 1: Iteration: 150 / 300 [ 50%]  (Warmup)
Chain 1: Iteration: 180 / 300 [ 60%]  (Warmup)
Chain 1: Iteration: 181 / 300 [ 60%]  (Sampling)
Chain 1: Iteration: 210 / 300 [ 70%]  (Sampling)
Chain 1: Iteration: 240 / 300 [ 80%]  (Sampling)
Chain 1: Iteration: 270 / 300 [ 90%]  (Sampling)
Chain 1: Iteration: 300 / 300 [100%]  (Sampling)
Chain 1: 
Chain 1:  Elapsed Time: 0.160541 seconds (Warm-up)
Chain 1:                0.056636 seconds (Sampling)
Chain 1:                0.217177 seconds (Total)
Chain 1: 

SAMPLING FOR MODEL '075149bad897790d99f42d10fc339899' NOW (CHAIN 2).
Chain 2: 
Chain 2: Gradient evaluation took 1.2e-05 seconds
Chain 2: 1000 transitions using 10 leapfrog steps per transition would take 0.12 seconds.
Chain 2: Adjust your expectations accordingly!
Chain 2: 
Chain 2: 
Chain 2: Iteration:   1 / 300 [  0%]  (Warmup)
Chain 2: Iteration:  30 / 300 [ 10%]  (Warmup)
Chain 2: Iteration:  60 / 300 [ 20%]  (Warmup)
Chain 2: Iteration:  90 / 300 [ 30%]  (Warmup)
Chain 2: Iteration: 120 / 300 [ 40%]  (Warmup)
Chain 2: Iteration: 150 / 300 [ 50%]  (Warmup)
Chain 2: Iteration: 180 / 300 [ 60%]  (Warmup)
Chain 2: Iteration: 181 / 300 [ 60%]  (Sampling)
Chain 2: Iteration: 210 / 300 [ 70%]  (Sampling)
Chain 2: Iteration: 240 / 300 [ 80%]  (Sampling)
Chain 2: Iteration: 270 / 300 [ 90%]  (Sampling)
Chain 2: Iteration: 300 / 300 [100%]  (Sampling)
Chain 2: 
Chain 2:  Elapsed Time: 0.117502 seconds (Warm-up)
Chain 2:                0.031148 seconds (Sampling)
Chain 2:                0.14865 seconds (Total)
Chain 2: 

SAMPLING FOR MODEL '075149bad897790d99f42d10fc339899' NOW (CHAIN 3).
Chain 3: 
Chain 3: Gradient evaluation took 9e-06 seconds
Chain 3: 1000 transitions using 10 leapfrog steps per transition would take 0.09 seconds.
Chain 3: Adjust your expectations accordingly!
Chain 3: 
Chain 3: 
Chain 3: Iteration:   1 / 300 [  0%]  (Warmup)
Chain 3: Iteration:  30 / 300 [ 10%]  (Warmup)
Chain 3: Iteration:  60 / 300 [ 20%]  (Warmup)
Chain 3: Iteration:  90 / 300 [ 30%]  (Warmup)
Chain 3: Iteration: 120 / 300 [ 40%]  (Warmup)
Chain 3: Iteration: 150 / 300 [ 50%]  (Warmup)
Chain 3: Iteration: 180 / 300 [ 60%]  (Warmup)
Chain 3: Iteration: 181 / 300 [ 60%]  (Sampling)
Chain 3: Iteration: 210 / 300 [ 70%]  (Sampling)
Chain 3: Iteration: 240 / 300 [ 80%]  (Sampling)
Chain 3: Iteration: 270 / 300 [ 90%]  (Sampling)
Chain 3: Iteration: 300 / 300 [100%]  (Sampling)
Chain 3: 
Chain 3:  Elapsed Time: 0.135886 seconds (Warm-up)
Chain 3:                0.058864 seconds (Sampling)
Chain 3:                0.19475 seconds (Total)
Chain 3: 
params_4 <- c('beta0', 'beta1', 'beta2', 'beta3', 'sigma')
traceplot(bayesion_model_4, pars = params_4, inc_warmup = TRUE)

4. Coeficientes

Coeficientes de la regresión múltiple:

lineal_model_4$coefficients
      (Intercept)    bill_length_mm     bill_depth_mm flipper_length_mm 
      -7582.81982          11.48403          75.08405          50.08308 

Coeficientes descubiertos por la regresión múltiple bayesiana:

for(param in params_4) print(get_posterior_mean(bayesion_model_4, par=param)[4])
[1] -6615.826
[1] 9.556223
[1] 56.53997
[1] 47.18177
[1] 260.8093

5. Validación

vars_4 <- c('bill_length_mm', 'bill_depth_mm', 'flipper_length_mm') 

lm_vs_br_models_validation(lineal_model_4, bayesion_model_4, params_4, vars_4, test_set)
bayesion_predictor_4 <- BayesianRegressionPredictor.from(bayesion_model_4, params_4, vars_4)

plot_compare_fit(
  lineal_model_4,
  bayesion_predictor_4, 
  train_set,
  label_1='Regresion Lineal', 
  label_2='Regresion Bayesiana'
)

Experimento 5

  • Igual al experimento 1 pero proponiendo dos nuevas regresiones bayesianas con priors para los parámetros que sean:
    • Una poca informativa (uniforme).
    • Una muy informativa (sesgada o con muy poca varianza).
  • Comparar con resultados de la bayesiana del experimento A

1. Modelo bayesiano con parametro con distribucion poco informativa

1. Modelo

Definimos una distribución uniforme para el beta asociado a la variable flipper_length_mm.

starting worker pid=16677 on localhost:11189 at 17:23:47.241
starting worker pid=16714 on localhost:11189 at 17:23:47.352
starting worker pid=16751 on localhost:11189 at 17:23:47.459

SAMPLING FOR MODEL '8d4f6f44cd52d0fbdc2d079b71ab6e36' NOW (CHAIN 1).
Chain 1: Rejecting initial value:
Chain 1:   Error evaluating the log probability at the initial value.
Chain 1: Exception: exponential_lpdf: Random variable is -0.453992, but must be >= 0!  (in 'model3ad83d61fbd8_8d4f6f44cd52d0fbdc2d079b71ab6e36' at line 20)

Chain 1: 
Chain 1: Gradient evaluation took 4.1e-05 seconds
Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 0.41 seconds.
Chain 1: Adjust your expectations accordingly!
Chain 1: 
Chain 1: 
Chain 1: Iteration:   1 / 1000 [  0%]  (Warmup)
Chain 1: Iteration: 100 / 1000 [ 10%]  (Warmup)
Chain 1: Iteration: 181 / 1000 [ 18%]  (Sampling)
Chain 1: Iteration: 280 / 1000 [ 28%]  (Sampling)
Chain 1: Iteration: 380 / 1000 [ 38%]  (Sampling)
Chain 1: Iteration: 480 / 1000 [ 48%]  (Sampling)

SAMPLING FOR MODEL '8d4f6f44cd52d0fbdc2d079b71ab6e36' NOW (CHAIN 2).
Chain 2: 
Chain 2: Gradient evaluation took 4.7e-05 seconds
Chain 2: 1000 transitions using 10 leapfrog steps per transition would take 0.47 seconds.
Chain 2: Adjust your expectations accordingly!
Chain 2: 
Chain 2: 
Chain 2: Iteration:   1 / 1000 [  0%]  (Warmup)
Chain 1: Iteration: 580 / 1000 [ 58%]  (Sampling)

SAMPLING FOR MODEL '8d4f6f44cd52d0fbdc2d079b71ab6e36' NOW (CHAIN 3).
Chain 3: 
Chain 3: Gradient evaluation took 4.2e-05 seconds
Chain 3: 1000 transitions using 10 leapfrog steps per transition would take 0.42 seconds.
Chain 3: Adjust your expectations accordingly!
Chain 3: 
Chain 3: 
Chain 3: Iteration:   1 / 1000 [  0%]  (Warmup)
Chain 1: Iteration: 680 / 1000 [ 68%]  (Sampling)
Chain 2: Iteration: 100 / 1000 [ 10%]  (Warmup)
Chain 1: Iteration: 780 / 1000 [ 78%]  (Sampling)
Chain 2: Iteration: 181 / 1000 [ 18%]  (Sampling)
Chain 1: Iteration: 880 / 1000 [ 88%]  (Sampling)
Chain 3: Iteration: 100 / 1000 [ 10%]  (Warmup)
Chain 2: Iteration: 280 / 1000 [ 28%]  (Sampling)
Chain 1: Iteration: 980 / 1000 [ 98%]  (Sampling)
Chain 1: Iteration: 1000 / 1000 [100%]  (Sampling)
Chain 1: 
Chain 1:  Elapsed Time: 0.654554 seconds (Warm-up)
Chain 1:                2.31789 seconds (Sampling)
Chain 1:                2.97244 seconds (Total)
Chain 1: 
Chain 3: Iteration: 181 / 1000 [ 18%]  (Sampling)
Chain 2: Iteration: 380 / 1000 [ 38%]  (Sampling)
Chain 2: Iteration: 480 / 1000 [ 48%]  (Sampling)
Chain 3: Iteration: 280 / 1000 [ 28%]  (Sampling)
Chain 2: Iteration: 580 / 1000 [ 58%]  (Sampling)
Chain 3: Iteration: 380 / 1000 [ 38%]  (Sampling)
Chain 2: Iteration: 680 / 1000 [ 68%]  (Sampling)
Chain 2: Iteration: 780 / 1000 [ 78%]  (Sampling)
Chain 3: Iteration: 480 / 1000 [ 48%]  (Sampling)
Chain 2: Iteration: 880 / 1000 [ 88%]  (Sampling)
Chain 3: Iteration: 580 / 1000 [ 58%]  (Sampling)
Chain 2: Iteration: 980 / 1000 [ 98%]  (Sampling)
Chain 2: Iteration: 1000 / 1000 [100%]  (Sampling)
Chain 2: 
Chain 2:  Elapsed Time: 0.890608 seconds (Warm-up)
Chain 2:                3.09286 seconds (Sampling)
Chain 2:                3.98347 seconds (Total)
Chain 2: 
Chain 3: Iteration: 680 / 1000 [ 68%]  (Sampling)
Chain 3: Iteration: 780 / 1000 [ 78%]  (Sampling)
Chain 3: Iteration: 880 / 1000 [ 88%]  (Sampling)
Chain 3: Iteration: 980 / 1000 [ 98%]  (Sampling)
Chain 3: Iteration: 1000 / 1000 [100%]  (Sampling)
Chain 3: 
Chain 3:  Elapsed Time: 1.14506 seconds (Warm-up)
Chain 3:                4.13633 seconds (Sampling)
Chain 3:                5.28139 seconds (Total)
Chain 3: 

2. Coeficientes

br_vs_br_coeficients(bayesion_model_1, bayesion_model_5, params_5)

3. Validación

lm_vs_br_models_validation(lineal_model_1, bayesion_model_1, params_1, vars_1, test_set)

4. Validacion

vars_5 <- c('bill_length_mm', 'bill_depth_mm', 'flipper_length_mm') 

lm_vs_br_models_validation(lineal_model_1, bayesion_model_5, params_5, vars_5, test_set)
bayesion_predictor_5 <- BayesianRegressionPredictor.from(bayesion_model_5, params_5, vars_5)

plot_compare_fit(
  bayesion_predictor_1,
  bayesion_predictor_5,
  train_set,
  label_1='Regresion Bayesiana con dist informativa', 
  label_2='Regresion Bayesiana con dist menos informativa'
)

2. Modelo bayesiano con parametro con distribucion muy informativa sesgada o con poca varianza

COMPLETAR

LS0tCnRpdGxlOiAiRW5mb3F1ZSBFc3RhZGlzdGljbyBkZWwgQXByZW5kaXphamUgLSBUcmFiYWpvIEZpbmFsIC0gUmVncmVzaW9uIEJheWVzaWFuYSIKZmlnX3dpZHRoOiAzIApmaWdfaGVpZ2h0OiAzIApvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGhpZ2hsaWdodDogcHlnbWVudHMKICAgIHRoZW1lOiBzYW5kc3RvbmUKICAgIHRvYzogeWVzCiAgICBkZl9wcmludDogcGFnZWQKICAgIGluY2x1ZGVzOgogICAgICBiZWZvcmVfYm9keTogLi9oZWFkZXIuaHRtbAogIGh0bWxfbm90ZWJvb2s6IAogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICBkZl9wcmludDogcGFnZWQKLS0tCgojIyMgQWRyaWFuIE1hcmlubwoKIyMjIENsYXVkaW8gQ29sbGFkbwoKCiMgSW5pY2lhbGl6YWNpw7NuCgpGaWphbW9zIGxhIHNlbWlsbGEgcGFyYSBwb2RlciByZXByb2R1Y2lyIGxvcyBleHBlcmltZW50b3MuIFRhbWJpw6luIHNlIGZpamEgZWwgbnVtZXJvIGRlIENQVSdzIGEgdXRpbGl6YXIuCgpgYGB7cn0Kc2V0LnNlZWQoNDIpCm9wdGlvbnMobWMuY29yZXMgPSAyNCkKYGBgCgoKIyBMaWJyZXJpYXMKClNlIGltcG9ydGFuIGxhcyBsaWJyZXLDrWFzIGEgdXRpbGl6YXIgYSBsbyBsYXJnbyBkZSBsYSBub3RlYm9vazoKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgaW5zdGFsbC5wYWNrYWdlcyhwYWNtYW4pCiMgaW5zdGFsbC5wYWNrYWdlcygiaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvc3JjL2NvbnRyaWIvcnN0YW5fMi4yMS4yLnRhci5neiIscmVwb3MgPSBOVUxMLHR5cGU9InNvdXJjZSIpCiMgc3VkbyBhcHQtZ2V0IGluc3RhbGwgbGliZ2xway1kZXYKYGBgCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHBhY21hbikKcF9sb2FkKHRpZHl2ZXJzZSwgdGlkeW1vZGVscywgcnNhbXBsZSwgcnN0YW4sIHNoaW55c3RhbiwgcnN0YW5hcm0sIGRldnRvb2xzKQpwX2xvYWRfZ2goJ2Fkcmlhbm1hcmluby9jb21tb25zJykKCmltcG9ydCgnLi4vc3JjL2RhdGFzZXQuUicpCmltcG9ydCgnLi4vc3JjL3Bsb3QuUicpCmltcG9ydCgnLi4vc3JjL21vZGVsLlInKQpgYGAKCiMgRGF0YXNldCB5IEFuYWxpc2lzIEV4cGxvcmF0b3JpbwoKIVtQYWxtZXIgUGVuZ3VpbnNdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9hbGxpc29uaG9yc3QvcGFsbWVycGVuZ3VpbnMvbWFzdGVyL21hbi9maWd1cmVzL2x0ZXJfcGVuZ3VpbnMucG5nKQoKW1BhbG1lciBQZW5ndWluc10oaHR0cHM6Ly9naXRodWIuY29tL3Jmb3JkYXRhc2NpZW5jZS90aWR5dHVlc2RheS9ibG9iL21hc3Rlci9kYXRhLzIwMjAvMjAyMC0wNy0yOC9yZWFkbWUubWQpCgoKIyMgMS4gTGVjdHVyYSBkZWwgZGF0YXNldAoKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmRhdGFzZXQgPC0gbG9hZF9kYXRhc2V0KCkgJT4lIG11dGF0ZV9pZihpcy5jaGFyYWN0ZXIsIGFzLmZhY3RvcikKCmRhdGFzZXQgJT4lIGdsaW1wc2UoKQpgYGAKCgojIyAyLiBWYXJpYWJsZXMKClNlIGVudW1lcmFuIHkgZGVzY3JpYmVuIGJyZXZlLW1lbnRlIGNhZGEgdmFyaWFibGUgcXVlIGZvcm1hIHBhcnRlIGRlbCBkYXRhc2V0OgoKVmFyaWFibGVzIE51bcOpcmljYXM6CgoqICoqYmlsbF9sZW5ndGhfbW0qKjogTG9uZ2l0dWQgZGVsIHBpY28gZGVsIGluZGl2aWR1byBtZWRpZGEgZW4gbWlsw61tZXRyb3MgKHRhbWJpw6luIGNvbm9jaWRhIGNvbW8gbG9uZ2l0dWQgZGVsIGN1bG1lbikuCiogKipiaWxsX2RlcHRoX21tKio6IFByb2Z1bmRpZGFkIGRlbCBwaWNvIGRlbCBpbmRpdmlkdW8gbWVkaWRhIGVuIG1pbMOtbWV0cm9zICh0YW1iacOpbiBjb25vY2lkYSBjb21vIHByb2Z1bmRpZGFkIGRlbCBjdWxtZW4pLgoqICoqZmxpcHBlcl9sZW5ndGhfbW0qKjogTG9uZ2l0dWQgZGUgbGEgYWxldGEgZGVsIGluZGl2aWR1byBtZWRpZGEgZW4gbWlsw61tZXRyb3MuCiogKipib2R5X21hc3NfZyoqOiBNYXNhIGNvcnBvcmFsIGRlbCBpbmRpdmlkdW8gbWVkaWRhIGVuIGdyYW1vcy4KKiAqKnllYXIqKjogQcOxbyBlbiBlbCBxdWUgc2UgcmVnaXN0cmEgZWwgaW5kaXZpZHVvLgoKVmFyaWFibGVzIENhdGVnw7NyaWNhczoKCiogKipzcGVjaWVzKio6IEVzcGVjaWUgZGVsIGluZGl2aWR1byAoQWRlbGllLCBHZW50b28gOykgbyBDaGluc3RyYXApLgoqICoqc2V4Kio6IFNleG8gZGVsIGluZGl2aWR1by4KKiAqKmlzbGFuZCoqOiBJc2xhIGRvbmRlIHNlIGVuY29udHLDqSBlbCBpbmRpdmlkdW8gKEJpc2NvZSwgRHJlYW0gbyBUb3JnZXJzZW4pLgoKQSBjb250aW51YWNpw7NuIHZlYW1vcyBsb3MgcG9zaWJsZXMgdmFsb3JlcyBkZSBsYXMgdmFyaWFibGVzIGNhdGVnw7NyaWNhczoKCmBgYHtyfQpzaG93X3ZhbHVlcyhkYXRhc2V0ICU+JSBzZWxlY3RfaWYobmVnYXRlKGlzLm51bWVyaWMpKSkKYGBgCgojIyAzLiBSZXN1bWVuIGRlIGZhbHRhbnRlcwoKYGBge3IgbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQptaXNzaW5nc19zdW1tYXJ5KGRhdGFzZXQpCmBgYAoKIyMgNC4gVmFyaWJsZXMgbnVtZXJpY2FzCgpgYGB7ciBmaWcuaGVpZ2h0PTIsIGZpZy53aWR0aD0zLCBtZXNzYWdlPVRSVUUsIHdhcm5pbmc9RkFMU0V9Cmhpc3RfcGxvdHMoZGF0YXNldCkKYGBgCgoKKipPYnNlcnZhY2lvbmVzKioKCiogU2UgYXByZWNpYSBxdWUgY2FkYSBhw7FvIHNlIHJlZ2lzdHJvIHByw6FjdGljYW1lbnRlIGVsIG1pc21vIG51bWVybyBkZSBpbmRpdmlkdW9zLgoqIExhIGRpc3RyaWJ1Y2nDs24gZGUgbGEgbWFzYSBjb3Jwb3JhbCBkZSBsb3MgaW5kaXZpZHVvcyB0aWVuZSB1bmEgYXNpbcOpdHJpY2EgcG9zaXRpdmEuIFRlbmVtb3MgbXVjaG9zIGluZGl2aWR1b3MgY29uIHZhbG9yZXMgYmFqb3MgZGUgbWFzYSBjb3Jwb3JhbCwgY29uIHVuYSBtZWRpYSBkZSAxOTIgZ3JhbW9zLiBMdWVnbyB0ZW5lbW9zIG1lbm9zIGluZGl2aWR1b3MgY29uIHZhbG9yZXMgbWFzIGFsdG8uCiogTGEgbG9uZ2l0dWQgZGUgbGEgYWxldGEgcGFyZWNlIHNlciB1bmEgZGlzdHJpYnVjacOzbiBiaS1tb2RhbC4gVGVuZW1vcyBkb3MgbW9kYXMgdW5hIDE5MiBtbSB5IG90cmEgZW4gMjE1IG1tLgoqIExhIGxvbmdpdHVkIGRlbCBwaWNvIHRhbWJpw6luIHBhcmVjZSB0ZW5lciB1bmEgbGlnZXJhIHNpbWV0csOtYSBwb3NpdGl2YS4gRXMgZGVjaXIgcXVlIGxvIGluZGl2aWR1b3MgY29uIG1lbm9yIHBlc28gdGllbmUgcGljbyBtYXMgcGVxdWXDsW9zLgoqIFBvciBvdHJvIGxhZG8gbGEgcHJvZnVuZGlkYWQgZGUgcGljbyBwYXJlY2UgdGVuZXIgdW5hIGxpZ2VyYSBzaW1ldHLDrWEgcG9zaXRpdmEuCgoKYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD04LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcid9CmJveF9wbG90cyhkYXRhc2V0KQpgYGAKCgoqKk9ic2VydmFjaW9uZXMqKgoKKiBDT01QTEVUQVIKCgojIyMgT3V0bGllcnMKCk5vIHNlIHJlZ2lzdHJhbiB2YWxvcmVzIG1hcyBleHRyZW1vcyBxdWUgZWwgbcOtbmltbyB5IG3DoXhpbW8gdmFsb3IgZW4gY2FkYSB2YXJpYWJsZXMuIEVzIGRlY2lyIHF1ZSBubyBlbmNvbnRyYW1vcyBvdXRsaWVycy4KCmBgYHtyLCBmaWcuc2hvdz0naGlkZSd9Cm91dGxpZXJzKGRhdGFzZXQsIGNvbHVtbj0nYmlsbF9sZW5ndGhfbW0nKQpgYGAKCgpgYGB7ciwgZmlnLnNob3c9J2hpZGUnfQpvdXRsaWVycyhkYXRhc2V0LCBjb2x1bW49J2JpbGxfbGVuZ3RoX21tJykKYGBgCgoKYGBge3IsIGZpZy5zaG93PSdoaWRlJ30Kb3V0bGllcnMoZGF0YXNldCwgY29sdW1uPSdiaWxsX2RlcHRoX21tJykKYGBgCgpgYGB7ciwgZmlnLnNob3c9J2hpZGUnfQpvdXRsaWVycyhkYXRhc2V0LCBjb2x1bW49J2ZsaXBwZXJfbGVuZ3RoX21tJykKYGBgCgpgYGB7ciwgZmlnLnNob3c9J2hpZGUnfQpvdXRsaWVycyhkYXRhc2V0LCBjb2x1bW49J2JvZHlfbWFzc19nJykKYGBgCgoKYGBge3IsIGZpZy5zaG93PSdoaWRlJ30Kb3V0bGllcnMoZGF0YXNldCwgY29sdW1uPSd5ZWFyJykKYGBgCgoKYGBge3IgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9Mywgd2FybmluZz1GQUxTRX0KYmFyX3Bsb3RzKGRhdGFzZXQpCmBgYAoKKipPYnNlcnZhY2lvbmVzKioKCiogTGEgdmFyaWFibGUgc2V4byBzZSBlbmN1ZW50cmEgYmFsYW5jZWFkYS4gUG9yIG90cm8gbGFkbywgY29udGllbmUgYWxndW5vcyB2YWxvcmVzIGZhbHRhbnRlcy4KKiBMYSB2YXJpYWJsZSBpc2xhbmQgZXN0YSBjb21wbGV0YW1lbnRlIGRlc2JhbGFuY2VhZGEuIEVzdG8gc2VndXJhbWVudGUgc2UgZGViZSBhIHVuYSBkaWZlcmVuY2lhIGVuIG51bWVybwplbiBsYXMgcG9ibGFjaW9uZXMgZW4gY2FkYSBpc2xhIG8gYSB1biBzZXNnbyBhbCBtb21lbnRvIGRlIHJlZ2lzdHJhciBsb3MgaW5kaXZpZHVvcy4gRXMgZGVjaXIgcXVlIHJlZ2lzdHJhbW9zIGNvbiBpbmRpdmlkdW9zIGVuIHVuYSBpc2xhIHF1ZSBlbiBvdHJhLgoqIExvIG1pc21vIHN1Y2VkZSBjb24gbGFzIGVzcGVjaWVzIGRlIGluZGl2aWR1b3MuIFZlbW9zIHVuIGdyYW4gZGVzYmFsYW5jZSBlbnRyZSBsYSBlc3BlY2llIENoaW5zdHJhcCB2cy4gb3RyYSBlc3BlY2llcy4gUG9yIG90cm8gYWxkbyBBZGVsaWUgeSBHZW50b28gdGllbmUgdW4gY29udGVvIG1hcyBjZXJjYW5vCgoKIyMgNS4gRXhjbHVpciBvYnNlcnZhY2lvbmVzIGNvbiBtaXNzaW5ncwoKYGBge3J9CmRhdGFzZXQgPC0gZGF0YXNldCAlPiUgZHJvcF9uYSgpCm1pc3NpbmdzX3N1bW1hcnkoZGF0YXNldCkKYGBgCgojIyA2LiBDb3JyZWxhY2lvbmVzCgpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJ30KY29ycl9wbG90KGRhdGFzZXQgJT4lIGRwbHlyOjpzZWxlY3QoLXllYXIpKQpgYGAKCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLmhlaWdodD0xMiwgZmlnLndpZHRoPTEyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcid9CnNlZ21lbnRlZF9wYWlyc19wbG90KGRhdGFzZXQsIHNlZ21lbnRfY29sdW1uPSdzcGVjaWVzJykKYGBgCgojIEV4cGVyaW1lbnRvcwoKIyMgRXhwZXJpbWVudG8gMQoKKiBTb2xvIHZhcmlhYmxlcyBudW3DqXJpY2FzLgoqIFJlZ3Jlc2nDs24gbcO6bHRpcGxlIGZyZWN1ZW50aXN0YS4KKiBSZWdyZXNpw7NuIG3Dumx0aXBsZSBiYXllc2lhbmEgY29uIHByaW9ycyBub3JtYWxlcyB5IGV4cG9uZW5jaWFsLgoKCiMjIyAxLiBTcGxpdCB0cmFpbiB2cy4gdGVzdAoKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnRyYWluX3Rlc3QgPC0gdHJhaW5fdGVzdF9zcGxpdChkYXRhc2V0LCB0cmFpbl9zaXplID0gMC43LCBzaHVmZmxlID0gVFJVRSkKdHJhaW5fc2V0IDwtIHRyYWluX3Rlc3RbWzFdXQp0ZXN0X3NldCAgPC0gdHJhaW5fdGVzdFtbMl1dCmBgYAoKCiMjIyAyLiBNb2RlbG8gbGluZWFsCgpgYGB7cn0KbGluZWFsX21vZGVsXzEgPC0gbG0oCiAgYm9keV9tYXNzX2cgfiBiaWxsX2xlbmd0aF9tbSArIGJpbGxfZGVwdGhfbW0gKyBmbGlwcGVyX2xlbmd0aF9tbSwKICBkYXRhID0gdHJhaW5fc2V0CikKYGBgCgojIyMgMy4gTW9kZWxvIGJheWVzaWFubwoKYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD04LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcid9CmJheWVzaW9uX21vZGVsXzEgPC0gc3RhbigKICBtb2RlbF9jb2RlID0gICIKICAgIGRhdGEgewogICAgICBpbnQ8bG93ZXI9MT4gICAgICAgICAgICAgICBvYnNfY291bnQ7CiAgICAgIHZlY3Rvcjxsb3dlcj0xPltvYnNfY291bnRdIHgxOwogICAgICB2ZWN0b3I8bG93ZXI9MT5bb2JzX2NvdW50XSB4MjsKICAgICAgdmVjdG9yPGxvd2VyPTE+W29ic19jb3VudF0geDM7CiAgICAgIHZlY3RvcltvYnNfY291bnRdICAgICAgICAgIHk7CiAgICB9CiAgICBwYXJhbWV0ZXJzIHsKICAgICAgcmVhbCAgICAgICAgICBiZXRhMDsKICAgICAgcmVhbCAgICAgICAgICBiZXRhMTsKICAgICAgcmVhbCAgICAgICAgICBiZXRhMjsKICAgICAgcmVhbCAgICAgICAgICBiZXRhMzsKICAgICAgcmVhbDxsb3dlcj0wPiBzaWdtYTsKICAgIH0KICAgIG1vZGVsIHsKICAgICAgYmV0YTAgfiBub3JtYWwoMCwgODAwMCk7CiAgICAgIGJldGExIH4gbm9ybWFsKDAsIDEwMCk7CiAgICAgIGJldGEyIH4gbm9ybWFsKDAsIDEwMCk7CiAgICAgIGJldGEzIH4gbm9ybWFsKDAsIDEwMCk7CiAgICAgIHNpZ21hIH4gZXhwb25lbnRpYWwoMC4xKTsKICAgIAogICAgICB5IH4gbm9ybWFsKGJldGEwICsgYmV0YTEgKiB4MSArIGJldGEyICogeDIgKyBiZXRhMyAqIHgzLCBzaWdtYSk7CiAgICB9CiAgIiwKICBkYXRhID0gbGlzdCgKICAgICAgb2JzX2NvdW50ID0gbnJvdyh0cmFpbl9zZXQpLAogICAgICB5ICA9IGNvbHZhbHVlcyh0cmFpbl9zZXQsICdib2R5X21hc3NfZycpLAogICAgICB4MSA9IGNvbHZhbHVlcyh0cmFpbl9zZXQsICdiaWxsX2xlbmd0aF9tbScpLAogICAgICB4MiA9IGNvbHZhbHVlcyh0cmFpbl9zZXQsICdiaWxsX2RlcHRoX21tJyksCiAgICAgIHgzID0gY29sdmFsdWVzKHRyYWluX3NldCwgJ2ZsaXBwZXJfbGVuZ3RoX21tJykKICApLAogIGNoYWlucyA9IDMsCiAgaXRlciAgID0gMzAwLAogIHdhcm11cCA9IDE4MCwKICB0aGluICAgPSAxCikKCnBhcmFtc18xIDwtIGMoJ2JldGEwJywgJ2JldGExJywgJ2JldGEyJywgJ2JldGEzJywgJ3NpZ21hJykKdHJhY2VwbG90KGJheWVzaW9uX21vZGVsXzEsIHBhcnMgPSBwYXJhbXNfMSwgaW5jX3dhcm11cCA9IFRSVUUpCmBgYAoKCiMjIyA0LiBDb2VmaWNpZW50ZXMKCmBgYHtyfQpsbV92c19icl9jb2VmaWNpZW50cyhsaW5lYWxfbW9kZWxfMSwgYmF5ZXNpb25fbW9kZWxfMSwgcGFyYW1zXzEpCmBgYAoKIyMjIDQuIFZhbGlkYWNpw7NuCgoKYGBge3J9CnZhcnNfMSA8LSBjKCdiaWxsX2xlbmd0aF9tbScsICdiaWxsX2RlcHRoX21tJywgJ2ZsaXBwZXJfbGVuZ3RoX21tJykgCgpsbV92c19icl9tb2RlbHNfdmFsaWRhdGlvbihsaW5lYWxfbW9kZWxfMSwgYmF5ZXNpb25fbW9kZWxfMSwgcGFyYW1zXzEsIHZhcnNfMSwgdGVzdF9zZXQpCmBgYAoKYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD04LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcid9CmJheWVzaW9uX3ByZWRpY3Rvcl8xIDwtIEJheWVzaWFuUmVncmVzc2lvblByZWRpY3Rvci5mcm9tKGJheWVzaW9uX21vZGVsXzEsIHBhcmFtc18xLCB2YXJzXzEpCgpwbG90X2NvbXBhcmVfZml0KAogIGxpbmVhbF9tb2RlbF8xLCAKICBiYXllc2lvbl9wcmVkaWN0b3JfMSwgCiAgdHJhaW5fc2V0LAogIGxhYmVsXzE9J1JlZ3Jlc2lvbiBMaW5lYWwnLCAKICBsYWJlbF8yPSdSZWdyZXNpb24gQmF5ZXNpYW5hJwopCmBgYAoKCiMjIEV4cGVyaW1lbnRvIDIKCiogSWRlbSBhIGV4cGVyaW1lbnRvIDEsIGluY29ycG9yYW5kbyB1bmEgdmFyaWFibGUgY2F0ZWfDs3JpY2EuCiogUmVncmVzacOzbiBtw7psdGlwbGUgZnJlY3VlbnRpc3RhLgoqIFJlZ3Jlc2nDs24gYmF5ZXNpYW5hIGNvbiBwcmlvcnMgbm9ybWFsZXMgeSBleHBvbmVuY2lhbC4KCgojIyMgMS4gTW9kZWxvIGxpbmVhbAoKCmBgYHtyfQpsaW5lYWxfbW9kZWxfMiA8LSBsbSgKICAgIGJvZHlfbWFzc19nIAogICAgICB+IGJpbGxfbGVuZ3RoX21tCiAgICAgICsgYmlsbF9kZXB0aF9tbQogICAgICArIGZsaXBwZXJfbGVuZ3RoX21tCiAgICAgICsgc2V4LAoKICBkYXRhID0gdHJhaW5fc2V0CikKYGBgCgojIyMgMi4gIE1vZGVsbyBiYXllc2lhbm8KCkFudGVzIHF1ZSBuYWRhIHRyYW5zZm9ybWFtb3MgbGEgY29sdW1uYSBjYXRlZ8OzcmljYSBhIG9uZS1ob3QgZW5jb2Rpbmc6CgpgYGB7cn0KY2F0X2NvbHMgPC0gYygnc2V4JykKCnRyYWluX3NldF9jYXQgPC0gdHJhaW5fc2V0ICU+JSBkdW1taWZ5KGNhdF9jb2xzKQp0ZXN0X3NldF9jYXQgIDwtIHRlc3Rfc2V0ICAlPiUgZHVtbWlmeShjYXRfY29scykKCnRyYWluX3NldF9jYXQKYGBgCgoKQ29uc3RydWltb3MgdW5hIG1hdHJpeiBjb24gdG9kYXMgbGFzIHZhcmlhYmxlcyhYKSBtYXMgZWwgaW50ZXJjZXB0OgoKYGBge3J9CnRvX21vZGVsX2lucHV0IDwtIGZ1bmN0aW9uKGRmKSB7CiAgbW9kZWwubWF0cml4KAogICAgYm9keV9tYXNzX2cgCiAgICAgIH4gYmlsbF9sZW5ndGhfbW0KICAgICAgKyBiaWxsX2RlcHRoX21tCiAgICAgICsgZmxpcHBlcl9sZW5ndGhfbW0KICAgICAgKyBzZXhfZmVtYWxlCiAgICAgICsgc2V4X21hbGUsCgogICAgZGF0YSA9IGRmCiAgKQp9Cgp0cmFpbl9YIDwtIHRyYWluX3NldF9jYXQgJT4lIHRvX21vZGVsX2lucHV0KCkKdGVzdF9YICA8LSB0ZXN0X3NldF9jYXQgJT4lIHRvX21vZGVsX2lucHV0KCkKYGBgCgoKRGVmaW5pbW9zIHkgY29ycmVtb3MgZWwgbW9kZWxvIGJheWVzaWFubzoKCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9OCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInfQpiYXllc2lvbl9tb2RlbF8yIDwtIHN0YW4oCiAgbW9kZWxfY29kZSA9ICIKICAgIGRhdGEgewogICAgICBpbnQ8bG93ZXI9MT4gICAgICAgICAgICAgICAgIG9ic19jb3VudDsKICAgICAgaW50PGxvd2VyPTE+ICAgICAgICAgICAgICAgICBjb2VmX2NvdW50OwogICAgICBtYXRyaXhbb2JzX2NvdW50LGNvZWZfY291bnRdIFg7CiAgICAgIHZlY3RvcltvYnNfY291bnRdICAgICAgICAgICAgeTsKICAgIH0KICAgIHBhcmFtZXRlcnMgewogICAgICB2ZWN0b3JbY29lZl9jb3VudF0gIGJldGE7CiAgICAgIHJlYWw8bG93ZXI9MD4gICAgICAgc2lnbWE7CiAgICB9CiAgICBtb2RlbCB7CiAgICAgIGJldGFbMV0gfiBub3JtYWwoMCwgMjAwMCk7CiAgICAgIAogICAgICBiZXRhWzJdIH4gbm9ybWFsKDAsIDMwKTsKICAgICAgYmV0YVszXSB+IG5vcm1hbCgwLCAxMDApOwogICAgICBiZXRhWzRdIH4gbm9ybWFsKDAsIDEwMCk7CiAgCiAgICAgIGJldGFbNV0gfiBub3JtYWwoMCwgMTAwKTsKICAgICAgYmV0YVs2XSB+IG5vcm1hbCgwLCAxMDApOwogIAogICAgICBzaWdtYSB+IGV4cG9uZW50aWFsKDAuMSk7CiAgCiAgICAgIHkgfiBub3JtYWwoWCAqIGJldGEsIHNpZ21hKTsKICAgIH0KICAiLAogIGRhdGEgPSBsaXN0KAogICAgICBvYnNfY291bnQgID0gZGltKHRyYWluX1gpWzFdLAogICAgICBjb2VmX2NvdW50ID0gZGltKHRyYWluX1gpWzJdLAogICAgICB5ICAgICAgICAgID0gY29sdmFsdWVzKHRyYWluX3NldF9jYXQsICdib2R5X21hc3NfZycpLAogICAgICBYICAgICAgICAgID0gdHJhaW5fWAogICksCiAgY2hhaW5zID0gMywKICBpdGVyICAgPSAzMDAsCiAgd2FybXVwID0gMTgwLAogIHRoaW4gICA9IDEKKQoKcGFyYW1zXzIgPC0gYygnYmV0YVsxXScsICdiZXRhWzJdJywgJ2JldGFbM10nLCAnYmV0YVs0XScsICdiZXRhWzVdJywgJ2JldGFbNl0nLCAnc2lnbWEnKQp0cmFjZXBsb3QoYmF5ZXNpb25fbW9kZWxfMiwgaW5jX3dhcm11cCA9IFRSVUUsIHBhcnMgPSBwYXJhbXNfMikKYGBgCgoKIyMjIDMuIENvZWZpY2llbnRlcwoKYGBge3J9CmxpbmVhbF9tb2RlbF8yJGNvZWZmaWNpZW50cwpgYGAKCmBgYHtyfQpicl9jb2VmaWNpZW50cyhiYXllc2lvbl9tb2RlbF8yLCBwYXJhbXNfMikKYGBgCgojIyMgNC4gVmFsaWRhY2nDs24KCmBgYHtyfQp2YXJzXzIgPC0gYygnYmlsbF9sZW5ndGhfbW0nLCAnYmlsbF9kZXB0aF9tbScsICdmbGlwcGVyX2xlbmd0aF9tbScsICdzZXhfZmVtYWxlJywnc2V4X21hbGUnKQoKbG1fdnNfYnJfbW9kZWxzX3ZhbGlkYXRpb24oCiAgbGluZWFsX21vZGVsXzIsIAogIGJheWVzaW9uX21vZGVsXzIsIAogIHBhcmFtc18yLAogIHZhcnNfMiwKICB0ZXN0X3NldCwgCiAgdGVzdF9zZXRfY2F0CikKYGBgCgpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTgsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJ30KYmF5ZXNpb25fcHJlZGljdG9yXzIgPC0gQmF5ZXNpYW5SZWdyZXNzaW9uUHJlZGljdG9yLmZyb20oYmF5ZXNpb25fbW9kZWxfMiwgcGFyYW1zXzIsIHZhcnNfMikKCnBsb3RfY29tcGFyZV9maXQoCiAgbGluZWFsX21vZGVsXzIsIAogIGJheWVzaW9uX3ByZWRpY3Rvcl8yLCAKICB0ZXN0X3NldCwgCiAgdGVzdF9zZXRfY2F0LAogIGxhYmVsXzE9J1JlZ3Jlc2lvbiBMaW5lYWwnLCAKICBsYWJlbF8yPSdSZWdyZXNpb24gQmF5ZXNpYW5hJwopCmBgYAoKIyMgRXhwZXJpbWVudG8gMwoKKiBJZGVtIGEgZXhwZXJpbWVudG8gMSBpbmNvcnBvcmFuZG8gb3V0bGllcnMgZW4gYWxndW5hIHZhcmlhYmxlIG51bcOpcmljYS4KKiBSZWdyZXNpw7NuIG3Dumx0aXBsZSBmcmVjdWVudGlzdGEuCiogUmVncmVzacOzbiBiYXllc2lhbmEgY29uIHByaW9ycyBub3JtYWxlcyB5IGV4cG9uZW5jaWFsLgoKCiMjIyAxLiBPdXRsaWVycwoKQSBjb250aW51YWNpw7NuIHZhbW9zIGEgYWdyZWdhciBvdXRsaWVycyBhIGxhIHZhcmlhYmxlICoqZmxpcHBlcl9sZW5ndGhfbW0qKiwgbGEgY3VhbCBkZWZpbmUgbGEgbG9uZ2l0dWQgZGUgbGEgYWxldGEgZGVsIGluZGl2aWR1byBtZWRpZGEgZW4gbWlsw61tZXRyb3MuIAoKUGFyYSB2aXN1YWxpemFyIGxvcyBudWV2b3Mgb3V0bGllcnMgYSBjb250aW51YWNpw7NuIGdyYWZpY2Ftb3MgKipmbGlwcGVyX2xlbmd0aF9tbSoqIHZzLiAgKipib2R5X21hc3NfZyoqIGFudGVzIHkgZGVzcHXDqXMgZGUgYWdyZWdhciBvdXRsaWVyczoKCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9NSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInfQpwbG90X2RhdGEodHJhaW5fc2V0KQpgYGAKCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9NSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInfQp0cmFpbl9zZXRfd2l0aF9vdXRsaWVycyA8LSB0cmFpbl9zZXQgJT4lCiAgbXV0YXRlKGZsaXBwZXJfbGVuZ3RoX21tID0gaWZlbHNlKAogICAgYm9keV9tYXNzX2cgPiA0OTAwICYgYm9keV9tYXNzX2cgPCA1MDAwLCAKICAgIGZsaXBwZXJfbGVuZ3RoX21tICsgKGZsaXBwZXJfbGVuZ3RoX21tICogcnVuaWYoMSwgMC4xLCAwLjIpKSwgCiAgICBmbGlwcGVyX2xlbmd0aF9tbQogICkpCgpwbG90X2RhdGEodHJhaW5fc2V0X3dpdGhfb3V0bGllcnMpCmBgYAoKIyMjIDIuIE1vZGVsbyBsaW5lYWwKCmBgYHtyfQpsaW5lYWxfbW9kZWxfMyA8LSBsbSgKICBib2R5X21hc3NfZyB+IGJpbGxfbGVuZ3RoX21tICsgYmlsbF9kZXB0aF9tbSArIGZsaXBwZXJfbGVuZ3RoX21tLAogIGRhdGEgPSB0cmFpbl9zZXRfd2l0aF9vdXRsaWVycwopCmBgYAoKCkNvbXBhcmVtb3MgZWwgYWp1c3RlIGRlbCBtb2RlbG8gbGluZWFsIGFqdXN0YW5kbyBlbiB1biBkYXRhc2V0IGRlIHRyYWluIGNvbiB5IHNpbiBvdXRsaWVyczoKCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9OCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInfQpwbG90X2NvbXBhcmVfZml0KAogIGxpbmVhbF9tb2RlbF8xLCAKICBsaW5lYWxfbW9kZWxfMywgCiAgdHJhaW5fc2V0X3dpdGhfb3V0bGllcnMsCiAgbGFiZWxfMT0nUmVncmVzacOzbiBMaW5lYWwgU0lOIG91dGxpZXJzJywgCiAgbGFiZWxfMj0nUmVncmVzacOzbiBMaW5lYWwgQ09OIG91dGxpdGVycycKKQpgYGAKIApTZSBhcHJlY2lhIHF1ZSBlbCBtb2RlbG8gZW50cmVuYWRvIGVuIGVsIHRyYWluIHNldCBvdXRsaWVycyBlc3RhIGFwYWxhbmNhZG8gcG9yIGxhcyBvYnNlcnZhY2lvbmVzIGF0aXBpY2FzLgoKCiMjIyAyLiBNb2RlbG8gbGluZWFsIFJvYnVzdG8KCkVudHJlbmFtb3MgdW5hIHJlZ3Jlc2nDs24gbGluZWFsIG3Dumx0aXBsZSByb2J1c3RhIHBhcmEgaW50ZW50YXIgZGUgZGlzbWludWlyIGVsIGVmZWN0byBkZSBsb3MgbnVldm9zIG91dGxpZXJzLgoKYGBge3J9CnBfbG9hZChNQVNTKQpyb2J1c3RfbGluZWFsX21vZGVsXzMgPC0gcmxtKAogIGJvZHlfbWFzc19nIH4gYmlsbF9sZW5ndGhfbW0gKyBiaWxsX2RlcHRoX21tICsgZmxpcHBlcl9sZW5ndGhfbW0sCiAgZGF0YSA9IHRyYWluX3NldF93aXRoX291dGxpZXJzCikKYGBgCgoKYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD04LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcid9CnBsb3RfY29tcGFyZV9maXQoCiAgbGluZWFsX21vZGVsXzEsIAogIHJvYnVzdF9saW5lYWxfbW9kZWxfMywgCiAgdHJhaW5fc2V0X3dpdGhfb3V0bGllcnMsCiAgbGFiZWxfMT0nUmVncmVzacOzbiBMaW5lYWwgU0lOIG91dGxpZXJzJywgCiAgbGFiZWxfMj0nUmVncmVzacOzbiBMaW5lYWwgUm9idXN0YSBDT04gb3V0bGl0ZXJzJwopCmBgYAoKR3LDoWZpY2FtZW50ZSBubyBzZSBsbGVnYSBhIGRpc3Rpbmd1aXIgcGVybyBlbCBtb2RlbG8gcm9idXN0byB0ZXJtaW5hIGFqdXN0YW5kbyBtZWpvciBxdWUgZWwgbW9kZWxvIGxpbmVhbCBjbMOhc2ljby4gRXN0byBzZSBwdWVkZSBvYnNlcnZhciBjdWFuZG8gY29tcGFyYW1vcyBlbCBSTVNFL01BRSBzb2JyZSB0cmFpbi4KCgpgYGB7cn0KbG1fdnNfbG1fbW9kZWxzX3ZhbGlkYXRpb24obGluZWFsX21vZGVsXzMsIHJvYnVzdF9saW5lYWxfbW9kZWxfMywgdHJhaW5fc2V0X3dpdGhfb3V0bGllcnMpCmBgYAoKTWFzIGFsbGEgZGUgZXN0byBlbCBtb2RlbG8gY2xhc2ljbyBzaWd1ZSBkYW5kbyBtZWpvcmVzIHJlc3VsdGFkbyBlbiB0ZXN0OgoKYGBge3J9CmxtX3ZzX2xtX21vZGVsc192YWxpZGF0aW9uKGxpbmVhbF9tb2RlbF8zLCByb2J1c3RfbGluZWFsX21vZGVsXzMsIHRlc3Rfc2V0KQpgYGAKCgojIyMgMy4gTW9kZWxvIGJheWVzaWFubyBjb24gTGlrZWxpaG9vZCBub3JtYWwKCgpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTgsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJ30KYmF5ZXNpb25fbW9kZWxfMyA8LSBzdGFuKAogIG1vZGVsX2NvZGUgPSAgIgogICAgZGF0YSB7CiAgICAgIGludDxsb3dlcj0xPiAgICAgICAgICAgICAgIG9ic19jb3VudDsKICAgICAgdmVjdG9yPGxvd2VyPTE+W29ic19jb3VudF0geDE7CiAgICAgIHZlY3Rvcjxsb3dlcj0xPltvYnNfY291bnRdIHgyOwogICAgICB2ZWN0b3I8bG93ZXI9MT5bb2JzX2NvdW50XSB4MzsKICAgICAgdmVjdG9yW29ic19jb3VudF0gICAgICAgICAgeTsKICAgIH0KICAgIHBhcmFtZXRlcnMgewogICAgICByZWFsICAgICAgICAgIGJldGEwOwogICAgICByZWFsICAgICAgICAgIGJldGExOwogICAgICByZWFsICAgICAgICAgIGJldGEyOwogICAgICByZWFsICAgICAgICAgIGJldGEzOwogICAgICByZWFsPGxvd2VyPTA+IHNpZ21hOwogICAgfQogICAgbW9kZWwgewogICAgICBiZXRhMCB+IG5vcm1hbCgwLCA4MDAwKTsKICAgICAgYmV0YTEgfiBub3JtYWwoMCwgMTAwKTsKICAgICAgYmV0YTIgfiBub3JtYWwoMCwgMTAwKTsKICAgICAgYmV0YTMgfiBub3JtYWwoMCwgMTAwKTsKICAgICAgc2lnbWEgfiBleHBvbmVudGlhbCgwLjEpOwogICAgCiAgICAgIHkgfiBub3JtYWwoYmV0YTAgKyBiZXRhMSAqIHgxICsgYmV0YTIgKiB4MiArIGJldGEzICogeDMsIHNpZ21hKTsKICAgIH0KICAiLAogIGRhdGEgPSBsaXN0KAogICAgICBvYnNfY291bnQgPSBucm93KHRyYWluX3NldF93aXRoX291dGxpZXJzKSwKICAgICAgeSAgPSBjb2x2YWx1ZXModHJhaW5fc2V0X3dpdGhfb3V0bGllcnMsICdib2R5X21hc3NfZycpLAogICAgICB4MSA9IGNvbHZhbHVlcyh0cmFpbl9zZXRfd2l0aF9vdXRsaWVycywgJ2JpbGxfbGVuZ3RoX21tJyksCiAgICAgIHgyID0gY29sdmFsdWVzKHRyYWluX3NldF93aXRoX291dGxpZXJzLCAnYmlsbF9kZXB0aF9tbScpLAogICAgICB4MyA9IGNvbHZhbHVlcyh0cmFpbl9zZXRfd2l0aF9vdXRsaWVycywgJ2ZsaXBwZXJfbGVuZ3RoX21tJykKICApLAogIGNoYWlucyA9IDMsCiAgaXRlciAgID0gMzAwLAogIHdhcm11cCA9IDE4MCwKICB0aGluICAgPSAxCikKCnBhcmFtc18zIDwtIGMoJ2JldGEwJywgJ2JldGExJywgJ2JldGEyJywgJ2JldGEzJywgJ3NpZ21hJykKdHJhY2VwbG90KGJheWVzaW9uX21vZGVsXzMsIHBhcnMgPSBwYXJhbXNfMywgaW5jX3dhcm11cCA9IFRSVUUpCmBgYAoKCiMjIyA0LiBDb2VmaWNpZW50ZXMKCmBgYHtyfQpsbV92c19icl9jb2VmaWNpZW50cyhyb2J1c3RfbGluZWFsX21vZGVsXzMsIGJheWVzaW9uX21vZGVsXzMsIHBhcmFtc18zKQpgYGAKCiMjIyA1LiBWYWxpZGFjacOzbgoKCmBgYHtyfQp2YXJzXzMgPC0gYygnYmlsbF9sZW5ndGhfbW0nLCAnYmlsbF9kZXB0aF9tbScsICdmbGlwcGVyX2xlbmd0aF9tbScpIAoKbG1fdnNfYnJfbW9kZWxzX3ZhbGlkYXRpb24ocm9idXN0X2xpbmVhbF9tb2RlbF8zLCBiYXllc2lvbl9tb2RlbF8zLCBwYXJhbXNfMywgdmFyc18zLCB0ZXN0X3NldCkKYGBgCgpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTgsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJ30KYmF5ZXNpb25fcHJlZGljdG9yXzMgPC0gQmF5ZXNpYW5SZWdyZXNzaW9uUHJlZGljdG9yLmZyb20oYmF5ZXNpb25fbW9kZWxfMywgcGFyYW1zXzMsIHZhcnNfMykKCnBsb3RfY29tcGFyZV9maXQoCiAgcm9idXN0X2xpbmVhbF9tb2RlbF8zLCAKICBiYXllc2lvbl9wcmVkaWN0b3JfMywgCiAgdHJhaW5fc2V0LAogIGxhYmVsXzE9J1JlZ3Jlc2lvbiBMaW5lYWwgUm9idXN0YSBDT04gb3V0bGllcnMnLCAKICBsYWJlbF8yPSdSZWdyZXNpb24gQmF5ZXNpYW5hIENPTiBvdXRsaWVycycKKQpgYGAKCgojIyMgNi4gTW9kZWxvIGJheWVzaWFubyBjb24gTGlrZWxpaG9vZCB0LXN0dWRlbnQKCgojIyBFeHBlcmltZW50byA0CgoKKiBJZGVtIGV4cGVyaW1lbnRvIDEgcGVybyByZWR1Y2llbmRvIGxhIGNhbnRpZGFkIGRlIG9ic2VydmFjaW9uZXMgYSBwb2NvcyB2YWxvcmVzIChlajozMCkuCiogUmVncmVzaW9uIG11bHRpcGxlIGZyZWN1ZW50aXN0YS4KKiBSZWdyZXNpb24gYmF5ZXNpYW5hIGNvbiBwcmlvcnMgbm9ybWFsZXMgeSBleHBvbmVuY2lhbC4KCgojIyMgMS4gU3BsaXQgdHJhaW4gLSB0ZXN0CgpFbiBlc3RlIGFzbyBlbnRyZW5hbW9zIHNvbG8gY29uIGVsIDEwJSBkZSBsbyBkYXRvcy4KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnRyYWluX3Rlc3QgPC0gdHJhaW5fdGVzdF9zcGxpdChkYXRhc2V0LCB0cmFpbl9zaXplID0gMC4wNSwgc2h1ZmZsZSA9IFRSVUUpCnRyYWluX3NldF80IDwtIHRyYWluX3Rlc3RbWzFdXQp0ZXN0X3NldF80ICA8LSB0cmFpbl90ZXN0W1syXV0KYGBgCgpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJ30KcGxvdF9kYXRhKHRyYWluX3NldF80KQpgYGAKCiMjIyAyLiBNb2RlbG8gbGluZWFsCgpgYGB7cn0KbGluZWFsX21vZGVsXzQgPC0gbG0oCiAgYm9keV9tYXNzX2cgfiBiaWxsX2xlbmd0aF9tbSArIGJpbGxfZGVwdGhfbW0gKyBmbGlwcGVyX2xlbmd0aF9tbSwKICBkYXRhID0gdHJhaW5fc2V0XzQKKQpgYGAKCiMjIyAzLiBNb2RlbG8gYmF5ZXNpYW5vCgpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTgsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJ30KYmF5ZXNpb25fbW9kZWxfNCA8LSBzdGFuKAogIG1vZGVsX2NvZGUgPSAgIgogICAgZGF0YSB7CiAgICAgIGludDxsb3dlcj0xPiAgICAgICAgICAgICAgIG9ic19jb3VudDsKICAgICAgdmVjdG9yPGxvd2VyPTE+W29ic19jb3VudF0geDE7CiAgICAgIHZlY3Rvcjxsb3dlcj0xPltvYnNfY291bnRdIHgyOwogICAgICB2ZWN0b3I8bG93ZXI9MT5bb2JzX2NvdW50XSB4MzsKICAgICAgdmVjdG9yW29ic19jb3VudF0gICAgICAgICAgeTsKICAgIH0KICAgIHBhcmFtZXRlcnMgewogICAgICByZWFsICAgICAgICAgIGJldGEwOwogICAgICByZWFsICAgICAgICAgIGJldGExOwogICAgICByZWFsICAgICAgICAgIGJldGEyOwogICAgICByZWFsICAgICAgICAgIGJldGEzOwogICAgICByZWFsPGxvd2VyPTA+IHNpZ21hOwogICAgfQogICAgbW9kZWwgewogICAgICBiZXRhMCB+IG5vcm1hbCgwLCA4MDAwKTsKICAgICAgYmV0YTEgfiBub3JtYWwoMCwgMTAwKTsKICAgICAgYmV0YTIgfiBub3JtYWwoMCwgMTAwKTsKICAgICAgYmV0YTMgfiBub3JtYWwoMCwgMTAwKTsKICAgICAgc2lnbWEgfiBleHBvbmVudGlhbCgwLjEpOwogICAgCiAgICAgIHkgfiBub3JtYWwoYmV0YTAgKyBiZXRhMSAqIHgxICsgYmV0YTIgKiB4MiArIGJldGEzICogeDMsIHNpZ21hKTsKICAgIH0KICAiLAogIGRhdGEgPSBsaXN0KAogICAgICBvYnNfY291bnQgPSBucm93KHRyYWluX3NldF80KSwKICAgICAgeSAgPSBjb2x2YWx1ZXModHJhaW5fc2V0XzQsICdib2R5X21hc3NfZycpLAogICAgICB4MSA9IGNvbHZhbHVlcyh0cmFpbl9zZXRfNCwgJ2JpbGxfbGVuZ3RoX21tJyksCiAgICAgIHgyID0gY29sdmFsdWVzKHRyYWluX3NldF80LCAnYmlsbF9kZXB0aF9tbScpLAogICAgICB4MyA9IGNvbHZhbHVlcyh0cmFpbl9zZXRfNCwgJ2ZsaXBwZXJfbGVuZ3RoX21tJykKICApLAogIGNoYWlucyA9IDMsCiAgaXRlciAgID0gMzAwLAogIHdhcm11cCA9IDE4MCwKICB0aGluICAgPSAxCikKCnBhcmFtc180IDwtIGMoJ2JldGEwJywgJ2JldGExJywgJ2JldGEyJywgJ2JldGEzJywgJ3NpZ21hJykKdHJhY2VwbG90KGJheWVzaW9uX21vZGVsXzQsIHBhcnMgPSBwYXJhbXNfNCwgaW5jX3dhcm11cCA9IFRSVUUpCmBgYAoKIyMjIDQuIENvZWZpY2llbnRlcwoKQ29lZmljaWVudGVzIGRlIGxhIHJlZ3Jlc2nDs24gbcO6bHRpcGxlOgoKCmBgYHtyfQpsaW5lYWxfbW9kZWxfNCRjb2VmZmljaWVudHMKYGBgCgpDb2VmaWNpZW50ZXMgZGVzY3ViaWVydG9zIHBvciBsYSByZWdyZXNpw7NuIG3Dumx0aXBsZSBiYXllc2lhbmE6CgpgYGB7cn0KZm9yKHBhcmFtIGluIHBhcmFtc180KSBwcmludChnZXRfcG9zdGVyaW9yX21lYW4oYmF5ZXNpb25fbW9kZWxfNCwgcGFyPXBhcmFtKVs0XSkKYGBgCgoKIyMjIDUuIFZhbGlkYWNpw7NuCgoKYGBge3J9CnZhcnNfNCA8LSBjKCdiaWxsX2xlbmd0aF9tbScsICdiaWxsX2RlcHRoX21tJywgJ2ZsaXBwZXJfbGVuZ3RoX21tJykgCgpsbV92c19icl9tb2RlbHNfdmFsaWRhdGlvbihsaW5lYWxfbW9kZWxfNCwgYmF5ZXNpb25fbW9kZWxfNCwgcGFyYW1zXzQsIHZhcnNfNCwgdGVzdF9zZXQpCmBgYAoKYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD01LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcid9CmJheWVzaW9uX3ByZWRpY3Rvcl80IDwtIEJheWVzaWFuUmVncmVzc2lvblByZWRpY3Rvci5mcm9tKGJheWVzaW9uX21vZGVsXzQsIHBhcmFtc180LCB2YXJzXzQpCgpwbG90X2NvbXBhcmVfZml0KAogIGxpbmVhbF9tb2RlbF80LAogIGJheWVzaW9uX3ByZWRpY3Rvcl80LCAKICB0cmFpbl9zZXQsCiAgbGFiZWxfMT0nUmVncmVzaW9uIExpbmVhbCcsIAogIGxhYmVsXzI9J1JlZ3Jlc2lvbiBCYXllc2lhbmEnCikKYGBgCgoKIyMgRXhwZXJpbWVudG8gNQoKKiBJZ3VhbCBhbCBleHBlcmltZW50byAxIHBlcm8gcHJvcG9uaWVuZG8gZG9zIG51ZXZhcyByZWdyZXNpb25lcyBiYXllc2lhbmFzIGNvbiBwcmlvcnMgcGFyYSBsb3MgcGFyw6FtZXRyb3MgcXVlIHNlYW46CiAgKiBVbmEgcG9jYSBpbmZvcm1hdGl2YSAodW5pZm9ybWUpLgogICogVW5hIG11eSBpbmZvcm1hdGl2YSAoc2VzZ2FkYSBvIGNvbiBtdXkgcG9jYSB2YXJpYW56YSkuCiogQ29tcGFyYXIgY29uIHJlc3VsdGFkb3MgZGUgbGEgYmF5ZXNpYW5hIGRlbCBleHBlcmltZW50byBBCgojIyMgMS4gTW9kZWxvIGJheWVzaWFubyBjb24gcGFyYW1ldHJvIGNvbiBkaXN0cmlidWNpb24gcG9jbyBpbmZvcm1hdGl2YQoKIyMjIyAxLiBNb2RlbG8KCkRlZmluaW1vcyB1bmEgW2Rpc3RyaWJ1Y2nDs24gdW5pZm9ybWVdKGh0dHBzOi8vbWMtc3Rhbi5vcmcvZG9jcy8yXzIxL2Z1bmN0aW9ucy1yZWZlcmVuY2UvdW5pZm9ybS1kaXN0cmlidXRpb24uaHRtbCkgcGFyYSBlbCBiZXRhIGFzb2NpYWRvIGEgbGEgdmFyaWFibGUgKipmbGlwcGVyX2xlbmd0aF9tbSoqLgoKYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD04LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcicsIGVjaG89RkFMU0V9CmJheWVzaW9uX21vZGVsXzUgPC0gc3RhbigKICBtb2RlbF9jb2RlID0gICIKICAgIGRhdGEgewogICAgICBpbnQ8bG93ZXI9MT4gICAgICAgICAgICAgICBvYnNfY291bnQ7CiAgICAgIHZlY3Rvcjxsb3dlcj0xPltvYnNfY291bnRdIHgxOwogICAgICB2ZWN0b3I8bG93ZXI9MT5bb2JzX2NvdW50XSB4MjsKICAgICAgdmVjdG9yPGxvd2VyPTE+W29ic19jb3VudF0geDM7CiAgICAgIHZlY3RvcltvYnNfY291bnRdICAgICAgICAgIHk7CiAgICB9CiAgICBwYXJhbWV0ZXJzIHsKICAgICAgcmVhbCAgICAgICAgICBiZXRhMDsKICAgICAgcmVhbCAgICAgICAgICBiZXRhMTsKICAgICAgcmVhbCAgICAgICAgICBiZXRhMjsKICAgICAgcmVhbCAgICAgICAgICBiZXRhMzsKICAgICAgcmVhbDxsb3dlcj0wPiBzaWdtYTsKICAgIH0KICAgIG1vZGVsIHsKICAgICAgYmV0YTAgfiBub3JtYWwoMCwgODAwMCk7CiAgICAgIGJldGExIH4gbm9ybWFsKDAsIDEwMCk7CiAgICAgIGJldGEyIH4gbm9ybWFsKDAsIDEwMCk7CiAgICAgIGJldGEzIH4gZXhwb25lbnRpYWwoMC4xKTsKICAgICAgc2lnbWEgfiBleHBvbmVudGlhbCgwLjUpOwogICAgCiAgICAgIHkgfiBub3JtYWwoYmV0YTAgKyBiZXRhMSAqIHgxICsgYmV0YTIgKiB4MiArIGJldGEzICogeDMsIHNpZ21hKTsKICAgIH0KICAiLAogIGRhdGEgPSBsaXN0KAogICAgICBvYnNfY291bnQgPSBucm93KHRyYWluX3NldCksCiAgICAgIHkgID0gY29sdmFsdWVzKHRyYWluX3NldCwgJ2JvZHlfbWFzc19nJyksCiAgICAgIHgxID0gY29sdmFsdWVzKHRyYWluX3NldCwgJ2JpbGxfbGVuZ3RoX21tJyksCiAgICAgIHgyID0gY29sdmFsdWVzKHRyYWluX3NldCwgJ2JpbGxfZGVwdGhfbW0nKSwKICAgICAgeDMgPSBjb2x2YWx1ZXModHJhaW5fc2V0LCAnZmxpcHBlcl9sZW5ndGhfbW0nKQogICksCiAgY2hhaW5zID0gMywKICBpdGVyICAgPSAxMDAwLAogIHdhcm11cCA9IDE4MCwKICB0aGluICAgPSAxCikKCnBhcmFtc181IDwtIGMoJ2JldGEwJywgJ2JldGExJywgJ2JldGEyJywgJ2JldGEzJywgJ3NpZ21hJykKdHJhY2VwbG90KGJheWVzaW9uX21vZGVsXzUsIHBhcnMgPSBwYXJhbXNfNSwgaW5jX3dhcm11cCA9IFRSVUUpCmBgYAoKCiMjIyMgMi4gQ29lZmljaWVudGVzCgoKYGBge3J9CmJyX3ZzX2JyX2NvZWZpY2llbnRzKGJheWVzaW9uX21vZGVsXzEsIGJheWVzaW9uX21vZGVsXzUsIHBhcmFtc181KQpgYGAKCgojIyMjIDMuIFZhbGlkYWNpw7NuCgpgYGB7cn0KbG1fdnNfYnJfbW9kZWxzX3ZhbGlkYXRpb24obGluZWFsX21vZGVsXzEsIGJheWVzaW9uX21vZGVsXzEsIHBhcmFtc18xLCB2YXJzXzEsIHRlc3Rfc2V0KQpgYGAKIyMjIyA0LiBWYWxpZGFjaW9uCgpgYGB7cn0KdmFyc181IDwtIGMoJ2JpbGxfbGVuZ3RoX21tJywgJ2JpbGxfZGVwdGhfbW0nLCAnZmxpcHBlcl9sZW5ndGhfbW0nKSAKCmxtX3ZzX2JyX21vZGVsc192YWxpZGF0aW9uKGxpbmVhbF9tb2RlbF8xLCBiYXllc2lvbl9tb2RlbF81LCBwYXJhbXNfNSwgdmFyc181LCB0ZXN0X3NldCkKYGBgCgpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTEwLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcid9CmJheWVzaW9uX3ByZWRpY3Rvcl81IDwtIEJheWVzaWFuUmVncmVzc2lvblByZWRpY3Rvci5mcm9tKGJheWVzaW9uX21vZGVsXzUsIHBhcmFtc181LCB2YXJzXzUpCgpwbG90X2NvbXBhcmVfZml0KAogIGJheWVzaW9uX3ByZWRpY3Rvcl8xLAogIGJheWVzaW9uX3ByZWRpY3Rvcl81LAogIHRyYWluX3NldCwKICBsYWJlbF8xPSdSZWdyZXNpb24gQmF5ZXNpYW5hIGNvbiBkaXN0IGluZm9ybWF0aXZhJywgCiAgbGFiZWxfMj0nUmVncmVzaW9uIEJheWVzaWFuYSBjb24gZGlzdCBtZW5vcyBpbmZvcm1hdGl2YScKKQpgYGAKCiMjIyAyLiBNb2RlbG8gYmF5ZXNpYW5vIGNvbiBwYXJhbWV0cm8gY29uIGRpc3RyaWJ1Y2lvbiBtdXkgaW5mb3JtYXRpdmEgc2VzZ2FkYSBvIGNvbiBwb2NhIHZhcmlhbnphCgpDT01QTEVUQVIKCgoKCiMjIFJlZmVyZW5jaWFzCgoqIFtNYWtpbmcgUHJlZGljdGlvbnMgZnJvbSBTdGFuIG1vZGVscyBpbiBSXShodHRwczovL21lZGl1bS5jb20vQGFsZXgucGF2bGFraXMvbWFraW5nLXByZWRpY3Rpb25zLWZyb20tc3Rhbi1tb2RlbHMtaW4tci0zZTM0OWRmYWMxZWQpCiogW0hvdyB0byByZXByZXNlbnQgYSBjYXRlZ29yaWNhbCBwcmVkaWN0b3IgcnN0YW4/XShodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8yOTE4MzU3Ny9ob3ctdG8tcmVwcmVzZW50LWEtY2F0ZWdvcmljYWwtcHJlZGljdG9yLXJzdGFuKQoqIFtSIGNvbW1vbnNdKGh0dHBzOi8vZ2l0aHViLmNvbS9hZHJpYW5tYXJpbm8vY29tbW9ucykKCg==